如何使用 JPA 和 Hibernate 将 Java Enum 映射到自定义值
文章目录
1、引言
在本文中,我们将探讨如何在使用 JPA 和 Hibernate 时,将 Java Enum 映射到自定义值。
虽然 Hibernate 提供了几种保存 Enum 值的选项,但能够自定义这个机制会更好,因为它可以让你更好地处理遗留应用程序或需要重新排序 Java Enum 值的用例。
2、数据模型
假设我们有如下的 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;
}
}
我们想要做的是存储 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);
}
}
现在,我们需要创建 PostStatusConverter
,它扩展 CustomOrdinalEnumConverter
基类并实现 convertToDatabaseColumn
方法:
@Converter
public class PostStatusConverter extends CustomOrdinalEnumConverter<PostStatus> {
public PostStatusConverter() {
super(PostStatus.class);
}
@Override
public Integer convertToDatabaseColumn(PostStatus enumValue) {
return enumValue.getStatusCode();
}
}
接下来,我们需要通过使用 @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;
}
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)
);
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);
当获取新持久化的 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());
很棒,对吧!
5、总结
如果你想在持久化和获取给定的 Enum 值时使用自定义序数值,JPA 允许你使用自定义 AttributeConverter
并提供自己的映射逻辑。
这种机制在处理遗留应用程序或需要重新排序 Enum 值时非常有用。例如,如果你的应用程序之前使用的是持久化到数据库中的默认序数值,重新排序 Enum 值会破坏应用程序,除非更新 post
表中的现有 Enum 列值或使用自定义 AttributeConverter
实例。