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);
}

验证要点

  1. 数据库字段存储格式符合转换规则("surname, name")
  2. 从数据库读取时能正确还原为 PersonName 对象
  3. 姓和名字段值保持完整

4. 总结

JPA 属性转换器提供了一种简单粗暴的解决方案,用于处理数据库字段与实体属性的类型转换问题。核心优势包括:

  • 类型安全:避免手动转换的潜在错误
  • 代码复用:转换逻辑集中管理
  • 零侵入性:通过注解即可启用

完整示例代码可在 GitHub 获取。建议在实际项目中优先使用此特性替代传统的 @PostLoad/@PreUpdate 回调方式。


原始标题:JPA Attribute Converters