我正在寻找使用 JPA 映射枚举的不同方法。我特别想设置每个枚举条目的整数值并只保存整数值。
@Entity
@Table(name = "AUTHORITY_")
public class Authority implements Serializable {
public enum Right {
READ(100), WRITE(200), EDITOR (300);
private int value;
Right(int value) { this.value = value; }
public int getValue() { return value; }
};
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "AUTHORITY_ID")
private Long id;
// the enum to map :
private Right right;
}
一个简单的解决方案是将 Enumerated 注解与 EnumType.ORDINAL 一起使用:
@Column(name = "RIGHT")
@Enumerated(EnumType.ORDINAL)
private Right right;
但在这种情况下,JPA 映射枚举索引 (0,1,2) 而不是我想要的值 (100,200,300)。
我发现的两个解决方案似乎并不简单......
第一个解决方案
解决方案 proposed here 使用 @PrePersist 和 @PostLoad 将枚举转换为其他字段并将枚举字段标记为瞬态:
@Basic
private int intValueForAnEnum;
@PrePersist
void populateDBFields() {
intValueForAnEnum = right.getValue();
}
@PostLoad
void populateTransientFields() {
right = Right.valueOf(intValueForAnEnum);
}
第二种解决方案
第二种解决方案 proposed here 提出了一个通用的转换对象,但看起来仍然很重且面向休眠(@Type 在 Java EE 中似乎不存在):
@Type(
type = "org.appfuse.tutorial.commons.hibernate.GenericEnumUserType",
parameters = {
@Parameter(
name = "enumClass",
value = "Authority$Right"),
@Parameter(
name = "identifierMethod",
value = "toInt"),
@Parameter(
name = "valueOfMethod",
value = "fromInt")
}
)
还有其他解决方案吗?
我有几个想法,但我不知道它们是否存在于 JPA 中:
加载和保存权限对象时使用权限类右成员的setter和getter方法
一个等效的想法是告诉 JPA Right enum 将 enum 转换为 int 并将 int 转换为 enum 的方法是什么
因为我使用的是 Spring,有没有办法告诉 JPA 使用特定的转换器(RightEditor)?
对于早于 JPA 2.1 的版本,JPA 仅提供两种处理枚举的方法,通过它们的 name
或通过它们的 ordinal
。并且标准 JPA 不支持自定义类型。所以:
如果您想进行自定义类型转换,则必须使用提供程序扩展(使用 Hibernate UserType、EclipseLink Converter 等)。 (第二种解决方案)。 〜或〜
您必须使用 @PrePersist 和 @PostLoad 技巧(第一个解决方案)。 〜或〜
注释 getter 和 setter 获取并返回 int 值 ~or~
在实体级别使用整数属性并在 getter 和 setter 中执行转换。
我将说明最新的选项(这是一个基本的实现,根据需要进行调整):
@Entity
@Table(name = "AUTHORITY_")
public class Authority implements Serializable {
public enum Right {
READ(100), WRITE(200), EDITOR (300);
private int value;
Right(int value) { this.value = value; }
public int getValue() { return value; }
public static Right parse(int id) {
Right right = null; // Default
for (Right item : Right.values()) {
if (item.getValue()==id) {
right = item;
break;
}
}
return right;
}
};
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "AUTHORITY_ID")
private Long id;
@Column(name = "RIGHT_ID")
private int rightId;
public Right getRight () {
return Right.parse(this.rightId);
}
public void setRight(Right right) {
this.rightId = right.getValue();
}
}
现在可以使用 JPA 2.1:
@Column(name = "RIGHT")
@Enumerated(EnumType.STRING)
private Right right;
更多详细信息:
https://dzone.com/articles/mapping-enums-done-right
http://www.thoughts-on-java.org/jpa-21-how-to-implement-type-converter/
@Converter
,但 enum
应该开箱即用地更优雅地处理!
AttributeConverter
但引用了一些代码,这些代码什么都不做,也不回答 OP。
AttributeConverter
已有答案
@Convert(converter = Converter.class) @Enumerated(EnumType.STRING)
从 JPA 2.1 开始,您可以使用 AttributeConverter。
像这样创建一个枚举类:
public enum NodeType {
ROOT("root-node"),
BRANCH("branch-node"),
LEAF("leaf-node");
private final String code;
private NodeType(String code) {
this.code = code;
}
public String getCode() {
return code;
}
}
并创建一个像这样的转换器:
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
@Converter(autoApply = true)
public class NodeTypeConverter implements AttributeConverter<NodeType, String> {
@Override
public String convertToDatabaseColumn(NodeType nodeType) {
return nodeType.getCode();
}
@Override
public NodeType convertToEntityAttribute(String dbData) {
for (NodeType nodeType : NodeType.values()) {
if (nodeType.getCode().equals(dbData)) {
return nodeType;
}
}
throw new IllegalArgumentException("Unknown database value:" + dbData);
}
}
在您只需要的实体上:
@Column(name = "node_type_code")
您对 @Converter(autoApply = true)
的运气可能因容器而异,但经测试可在 Wildfly 8.1.0 上运行。如果它不起作用,您可以在实体类列上添加 @Convert(converter = NodeTypeConverter.class)
。
@Converter(autoApply = true)
和 @Convert(converter = NodeTypeConverter.class)
放在一起,还是必须是 @Converter
或 @Convert
并且两者不能一起使用?
@PersistenceValue("root-node") ROOT,
的事情,或者类似的事情。
最好的方法是将唯一的 ID 映射到每个枚举类型,从而避免 ORDINAL 和 STRING 的陷阱。请参阅此 post,其中概述了映射枚举的 5 种方法。
摘自上面的链接:
1&2。使用@Enumerated
目前有 2 种方法可以使用 @Enumerated 注释在 JPA 实体中映射枚举。不幸的是,EnumType.STRING 和 EnumType.ORDINAL 都有其局限性。
如果您使用 EnumType.String,那么重命名您的枚举类型之一将导致您的枚举值与保存在数据库中的值不同步。如果您使用 EnumType.ORDINAL 则删除或重新排序枚举中的类型将导致保存在数据库中的值映射到错误的枚举类型。
这两种选择都很脆弱。如果枚举在未执行数据库迁移的情况下被修改,您可能会危及数据的完整性。
3. 生命周期回调
一种可能的解决方案是使用 JPA 生命周期回调注释 @PrePersist 和 @PostLoad。这感觉非常难看,因为您的实体中现在将有两个变量。一个映射存储在数据库中的值,另一个映射实际的枚举。
4. 将唯一 ID 映射到每个枚举类型
首选的解决方案是将您的枚举映射到枚举中定义的固定值或 ID。映射到预定义的固定值使您的代码更加健壮。对枚举类型顺序的任何修改,或者对名称的重构,都不会造成任何不利影响。
5. 使用 Java EE7 @Convert
如果您使用的是 JPA 2.1,则可以选择使用新的 @Convert 注释。这需要创建一个使用@Converter 注释的转换器类,您可以在其中定义每种枚举类型将哪些值保存到数据库中。然后,在您的实体中,您将使用 @Convert 注释您的枚举。
我的偏好:(4号)
我更喜欢在枚举中定义我的 ID 而不是使用转换器的原因是良好的封装。只有枚举类型应该知道它的 ID,只有实体应该知道它如何将枚举映射到数据库。
有关代码示例,请参见原始 post。
问题是,我认为,JPA 从来没有考虑到我们可以拥有一个复杂的预先存在的模式。
我认为这导致了 Enum 特有的两个主要缺点:
使用 name() 和 ordinal() 的限制。为什么不像我们使用@Entity 那样用@Id 标记getter?枚举通常在数据库中有表示,以允许与各种元数据关联,包括专有名称、描述性名称,可能还有本地化等。我们需要枚举的易用性和实体的灵活性。
帮助我的事业并在 JPA_SPEC-47 上投票
这不会比使用@Converter 解决问题更优雅吗?
// Note: this code won't work!!
// it is just a sample of how I *would* want it to work!
@Enumerated
public enum Language {
ENGLISH_US("en-US"),
ENGLISH_BRITISH("en-BR"),
FRENCH("fr"),
FRENCH_CANADIAN("fr-CA");
@ID
private String code;
@Column(name="DESCRIPTION")
private String description;
Language(String code) {
this.code = code;
}
public String getCode() {
return code;
}
public String getDescription() {
return description;
}
}
我自己解决这种 Enum JPA 映射的解决方案如下。
第 1 步 - 编写以下接口,我们将用于我们想要映射到 db 列的所有枚举:
public interface IDbValue<T extends java.io.Serializable> {
T getDbVal();
}
第 2 步 - 实现自定义通用 JPA 转换器,如下所示:
import javax.persistence.AttributeConverter;
public abstract class EnumDbValueConverter<T extends java.io.Serializable, E extends Enum<E> & IDbValue<T>>
implements AttributeConverter<E, T> {
private final Class<E> clazz;
public EnumDbValueConverter(Class<E> clazz){
this.clazz = clazz;
}
@Override
public T convertToDatabaseColumn(E attribute) {
if (attribute == null) {
return null;
}
return attribute.getDbVal();
}
@Override
public E convertToEntityAttribute(T dbData) {
if (dbData == null) {
return null;
}
for (E e : clazz.getEnumConstants()) {
if (dbData.equals(e.getDbVal())) {
return e;
}
}
// handle error as you prefer, for example, using slf4j:
// log.error("Unable to convert {} to enum {}.", dbData, clazz.getCanonicalName());
return null;
}
}
此类将通过使用枚举 E
上的 getDbVal()
将枚举值 E
转换为类型为 T
的数据库字段(例如 String
),反之亦然。
第 3 步 - 让原始枚举实现我们在第 1 步中定义的接口:
public enum Right implements IDbValue<Integer> {
READ(100), WRITE(200), EDITOR (300);
private final Integer dbVal;
private Right(Integer dbVal) {
this.dbVal = dbVal;
}
@Override
public Integer getDbVal() {
return dbVal;
}
}
第 4 步 - 为第 3 步的 Right
枚举扩展第 2 步的转换器:
public class RightConverter extends EnumDbValueConverter<Integer, Right> {
public RightConverter() {
super(Right.class);
}
}
第 5 步 - 最后一步是注释实体中的字段,如下所示:
@Column(name = "RIGHT")
@Convert(converter = RightConverter.class)
private Right right;
结论
恕我直言,如果您有许多要映射的枚举并且您想使用枚举本身的特定字段作为映射值,那么这是最干净和最优雅的解决方案。
对于您项目中需要类似映射逻辑的所有其他枚举,您只需重复步骤 3 到 5,即:
在您的枚举上实现接口 IDbValue;
仅用 3 行代码扩展 EnumDbValueConverter(您也可以在实体中执行此操作以避免创建单独的类);
使用 javax.persistence 包中的 @Convert 注释枚举属性。
希望这可以帮助。
可能关闭Pascal的相关代码
@Entity
@Table(name = "AUTHORITY_")
public class Authority implements Serializable {
public enum Right {
READ(100), WRITE(200), EDITOR(300);
private Integer value;
private Right(Integer value) {
this.value = value;
}
// Reverse lookup Right for getting a Key from it's values
private static final Map<Integer, Right> lookup = new HashMap<Integer, Right>();
static {
for (Right item : Right.values())
lookup.put(item.getValue(), item);
}
public Integer getValue() {
return value;
}
public static Right getKey(Integer value) {
return lookup.get(value);
}
};
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "AUTHORITY_ID")
private Long id;
@Column(name = "RIGHT_ID")
private Integer rightId;
public Right getRight() {
return Right.getKey(this.rightId);
}
public void setRight(Right right) {
this.rightId = right.getValue();
}
}
我会做以下事情:
在它自己的文件中单独声明枚举:
public enum RightEnum {
READ(100), WRITE(200), EDITOR (300);
private int value;
private RightEnum (int value) { this.value = value; }
@Override
public static Etapa valueOf(Integer value){
for( RightEnum r : RightEnum .values() ){
if ( r.getValue().equals(value))
return r;
}
return null;//or throw exception
}
public int getValue() { return value; }
}
声明一个名为 Right 的新 JPA 实体
@Entity
public class Right{
@Id
private Integer id;
//FIElDS
// constructor
public Right(RightEnum rightEnum){
this.id = rightEnum.getValue();
}
public Right getInstance(RightEnum rightEnum){
return new Right(rightEnum);
}
}
您还需要一个转换器来接收此值(仅限 JPA 2.1,并且存在一个问题,我不会在这里讨论这些枚举,以便使用转换器直接持久化,因此它只是单向路)
import mypackage.RightEnum;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
/**
*
*
*/
@Converter(autoApply = true)
public class RightEnumConverter implements AttributeConverter<RightEnum, Integer>{
@Override //this method shoudn´t be used, but I implemented anyway, just in case
public Integer convertToDatabaseColumn(RightEnum attribute) {
return attribute.getValue();
}
@Override
public RightEnum convertToEntityAttribute(Integer dbData) {
return RightEnum.valueOf(dbData);
}
}
管理局实体:
@Entity
@Table(name = "AUTHORITY_")
public class Authority implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "AUTHORITY_ID")
private Long id;
// the **Entity** to map :
private Right right;
// the **Enum** to map (not to be persisted or updated) :
@Column(name="COLUMN1", insertable = false, updatable = false)
@Convert(converter = RightEnumConverter.class)
private RightEnum rightEnum;
}
通过这种方式,您不能直接设置为枚举字段。但是,您可以使用权限设置权限字段
autorithy.setRight( Right.getInstance( RightEnum.READ ) );//for example
如果需要比较,可以使用:
authority.getRight().equals( RightEnum.READ ); //for example
我认为这很酷。这并不完全正确,因为转换器不打算与枚举一起使用。实际上,文档说永远不要将它用于此目的,您应该改用 @Enumerated 注释。问题是只有两种枚举类型:ORDINAL 或 STRING,但 ORDINAL 很棘手且不安全。
但是,如果它不能满足您的要求,您可以做一些更老套和更简单(或不这样做)的事情。
让我们来看看。
RightEnum:
public enum RightEnum {
READ(100), WRITE(200), EDITOR (300);
private int value;
private RightEnum (int value) {
try {
this.value= value;
final Field field = this.getClass().getSuperclass().getDeclaredField("ordinal");
field.setAccessible(true);
field.set(this, value);
} catch (Exception e) {//or use more multicatch if you use JDK 1.7+
throw new RuntimeException(e);
}
}
@Override
public static Etapa valueOf(Integer value){
for( RightEnum r : RightEnum .values() ){
if ( r.getValue().equals(value))
return r;
}
return null;//or throw exception
}
public int getValue() { return value; }
}
和管理局实体
@Entity
@Table(name = "AUTHORITY_")
public class Authority implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "AUTHORITY_ID")
private Long id;
// the **Enum** to map (to be persisted or updated) :
@Column(name="COLUMN1")
@Enumerated(EnumType.ORDINAL)
private RightEnum rightEnum;
}
在第二个想法中,它不是一个完美的情况,因为我们破解了序数属性,但它的编码要小得多。
我认为 JPA 规范应该包括 EnumType.ID ,其中枚举值字段应该用某种 @EnumId 注释进行注释。
不定期副业成功案例分享