如何使用 JPA 和 Hibernate 将 Java Enum 映射到自定义值


文章目录
  • 如何使用 JPA 和 Hibernate 将 Java Enum 映射到自定义值
  • 1、引言
  • 2、数据模型
  • 3、如何使用 JPA 和 Hibernate 将 Java Enum 映射到自定义值
  • 4、代码测试
  • 5、总结


1、引言

在本文中,我们将探讨如何在使用 JPA 和 Hibernate 时,将 Java Enum 映射到自定义值。

虽然 Hibernate 提供了几种保存 Enum 值的选项,但能够自定义这个机制会更好,因为它可以让你更好地处理遗留应用程序或需要重新排序 Java Enum 值的用例。

2、数据模型

如何使用 JPA 和 Hibernate 将 Java Enum 映射到自定义值_python

假设我们有如下的 post 表:

id

title

status

1

To be moderated

1

2

Pending

100

3

Approved

10

4

Spam post

50

status 列存储与给定 PostStatus Enum 值相关联的数值,但该值不是 Java Enum 对象的典型序数值。

PostStatus Java Enum 如下:

public enum PostStatus {
    PENDING(100),
    APPROVED(10),
    SPAM(50),
    REQUIRES_MODERATOR_INTERVENTION(1);

    private final int statusCode;

    PostStatus(int statusCode) {
        this.statusCode = statusCode;
    }

    public int getStatusCode() {
        return statusCode;
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

我们想要做的是存储 PostStatus Enum 的自定义 statusCode,而不是典型的 Java Enum 序数或名称值。

3、如何使用 JPA 和 Hibernate 将 Java Enum 映射到自定义值

默认情况下,Hibernate 使用 EnumType 来确定是使用 Enum 名称还是序数来持久化 Enum 到底层数据库列中。

JPA 提供了 AttributeConverter 抽象,帮助我们在希望控制某个基本类型如何在数据库表列中持久化时使用。

为了实现使用自定义序数值的目标,我们将使用 Hypersistence Utils 项目中的 CustomOrdinalEnumConverter,其代码如下:

public abstract class CustomOrdinalEnumConverter<T> implements AttributeConverter<T, Integer> {
    
    private Map<Integer, T> customOrdinalValueToEnumMap = new HashMap<>();

    /**
     * 初始化构造函数,接受要管理的 Java Enum。
     *
     * @param enumType 要管理的 Java Enum 类型
     */
    public CustomOrdinalEnumConverter(Class<T> enumType) {
        T[] enumValues = ReflectionUtils.invokeStaticMethod(
            ReflectionUtils.getMethod(enumType, "values")
        );
        for (T enumValue : enumValues) {
            Integer customOrdinalValue = convertToDatabaseColumn(enumValue);
            customOrdinalValueToEnumMap.put(customOrdinalValue, enumValue);
        }
    }

    @Override
    public T convertToEntityAttribute(Integer ordinalValue) {
        return customOrdinalValueToEnumMap.get(ordinalValue);
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.

现在,我们需要创建 PostStatusConverter,它扩展 CustomOrdinalEnumConverter 基类并实现 convertToDatabaseColumn 方法:

@Converter
public class PostStatusConverter extends CustomOrdinalEnumConverter<PostStatus> {

    public PostStatusConverter() {
        super(PostStatus.class);
    }

    @Override
    public Integer convertToDatabaseColumn(PostStatus enumValue) {
        return enumValue.getStatusCode();
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

接下来,我们需要通过使用 @Convert 注解来指示 Hibernate 使用 PostStatusConverter 处理 PostStatus 实体属性:

@Entity(name = "Post")
@Table(name = "post")
public class Post {

    @Id
    private Integer id;

    @Column(length = 250)
    private String title;

    @Column(columnDefinition = "NUMERIC(3)")
    @Convert(converter = PostStatusConverter.class)
    private PostStatus status;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

4、代码测试

当持久化以下 Post 实体时:

entityManager.persist(
    new Post()
        .setId(1)
        .setTitle("To be moderated")
        .setStatus(PostStatus.REQUIRES_MODERATOR_INTERVENTION)
);
entityManager.persist(
    new Post()
        .setId(2)
        .setTitle("Pending")
        .setStatus(PostStatus.PENDING)
);
entityManager.persist(
    new Post()
        .setId(3)
        .setTitle("Approved")
        .setStatus(PostStatus.APPROVED)
);
entityManager.persist(
    new Post()
        .setId(4)
        .setTitle("Spam post")
        .setStatus(PostStatus.SPAM)
);
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.

Hibernate 生成以下 SQL INSERT 语句:

INSERT INTO post (status, title, id) VALUES (1, 'To be moderated', 1);
INSERT INTO post (status, title, id) VALUES (100, 'Pending', 2);
INSERT INTO post (status, title, id) VALUES (10, 'Approved', 3);
INSERT INTO post (status, title, id) VALUES (50, 'Spam post', 4);
  • 1.
  • 2.
  • 3.
  • 4.

当获取新持久化的 Post 实体时,我们可以看到 status 属性被正确地获取:

assertEquals(PostStatus.REQUIRES_MODERATOR_INTERVENTION, entityManager.find(Post.class, 1).getStatus());
assertEquals(PostStatus.PENDING, entityManager.find(Post.class, 2).getStatus());
assertEquals(PostStatus.APPROVED, entityManager.find(Post.class, 3).getStatus());
assertEquals(PostStatus.SPAM, entityManager.find(Post.class, 4).getStatus());
  • 1.
  • 2.
  • 3.
  • 4.

很棒,对吧!

5、总结

如果你想在持久化和获取给定的 Enum 值时使用自定义序数值,JPA 允许你使用自定义 AttributeConverter 并提供自己的映射逻辑。

这种机制在处理遗留应用程序或需要重新排序 Enum 值时非常有用。例如,如果你的应用程序之前使用的是持久化到数据库中的默认序数值,重新排序 Enum 值会破坏应用程序,除非更新 post 表中的现有 Enum 列值或使用自定义 AttributeConverter 实例。