1. 引言
本文将带你快速掌握 JPA 3.0 中属性转换器(Attribute Converters)的使用技巧。简单来说,这个特性允许我们将 JDBC 类型映射到自定义 Java 类,解决数据库字段与实体属性类型不匹配的常见问题。本文基于 Hibernate 6 演示实现方案。
2. 创建转换器
我们通过一个实际案例展示如何为自定义 Java 类实现属性转换器。
2.1 定义被转换类
首先创建需要被转换的 PersonName
类:
public class PersonName implements Serializable {
private String name;
private String surname;
// getters and setters
}
2.2 实体类集成
将 PersonName
作为属性添加到 @Entity
类中:
@Entity(name = "PersonTable")
public class Person {
private PersonName personName;
//...
}
2.3 实现转换器
现在创建转换器,将 PersonName
与数据库字符串字段双向转换。核心步骤如下:
✅ 必须实现 AttributeConverter
接口
✅ 使用 @Converter
注解标记转换器类
✅ 实现两个核心方法:
convertToDatabaseColumn()
:Java对象 → 数据库字段convertToEntityAttribute()
:数据库字段 → Java对象
完整实现代码:
@Converter
public class PersonNameConverter implements
AttributeConverter<PersonName, String> {
private static final String SEPARATOR = ", ";
@Override
public String convertToDatabaseColumn(PersonName personName) {
if (personName == null) {
return null;
}
StringBuilder sb = new StringBuilder();
if (personName.getSurname() != null && !personName.getSurname()
.isEmpty()) {
sb.append(personName.getSurname());
sb.append(SEPARATOR);
}
if (personName.getName() != null
&& !personName.getName().isEmpty()) {
sb.append(personName.getName());
}
return sb.toString();
}
@Override
public PersonName convertToEntityAttribute(String dbPersonName) {
if (dbPersonName == null || dbPersonName.isEmpty()) {
return null;
}
String[] pieces = dbPersonName.split(SEPARATOR);
if (pieces == null || pieces.length == 0) {
return null;
}
PersonName personName = new PersonName();
String firstPiece = !pieces[0].isEmpty() ? pieces[0] : null;
if (dbPersonName.contains(SEPARATOR)) {
personName.setSurname(firstPiece);
if (pieces.length >= 2 && pieces[1] != null
&& !pieces[1].isEmpty()) {
personName.setName(pieces[1]);
}
} else {
personName.setName(firstPiece);
}
return personName;
}
}
⚠️ 踩坑提示:转换逻辑必须处理 null 值和空字符串,否则运行时可能抛出 NPE。
3. 使用转换器
3.1 实体类配置
在实体类属性上添加 @Convert
注解,指定转换器类:
@Entity(name = "PersonTable")
public class Person {
@Convert(converter = PersonNameConverter.class)
private PersonName personName;
// ...
}
3.2 功能验证
通过单元测试验证转换逻辑:
3.2.1 数据存储测试
@Test
public void givenPersonName_whenSaving_thenNameAndSurnameConcat() {
String name = "name";
String surname = "surname";
PersonName personName = new PersonName();
personName.setName(name);
personName.setSurname(surname);
Person person = new Person();
person.setPersonName(personName);
Long id = (Long) session.save(person);
session.flush();
session.clear();
}
3.2.2 数据库字段验证
@Test
public void givenPersonName_whenSaving_thenNameAndSurnameConcat() {
// ... 前置代码同上
String dbPersonName = (String) session.createNativeQuery(
"select p.personName from PersonTable p where p.id = :id")
.setParameter("id", id)
.getSingleResult();
assertEquals(surname + ", " + name, dbPersonName);
}
3.2.3 对象转换验证
@Test
public void givenPersonName_whenSaving_thenNameAndSurnameConcat() {
// ... 前置代码同上
Person dbPerson = session.createNativeQuery(
"select * from PersonTable p where p.id = :id", Person.class)
.setParameter("id", id)
.getSingleResult();
assertEquals(dbPerson.getPersonName()
.getName(), name);
assertEquals(dbPerson.getPersonName()
.getSurname(), surname);
}
✅ 验证要点:
- 数据库字段存储格式符合转换规则("surname, name")
- 从数据库读取时能正确还原为
PersonName
对象 - 姓和名字段值保持完整
4. 总结
JPA 属性转换器提供了一种简单粗暴的解决方案,用于处理数据库字段与实体属性的类型转换问题。核心优势包括:
- ✅ 类型安全:避免手动转换的潜在错误
- ✅ 代码复用:转换逻辑集中管理
- ✅ 零侵入性:通过注解即可启用
完整示例代码可在 GitHub 获取。建议在实际项目中优先使用此特性替代传统的 @PostLoad
/@PreUpdate
回调方式。