使用固定值在JPA映射枚举?

我正在寻找使用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; } 

一个简单的解决scheme是使用枚举注释与EnumType.ORDINAL:

 @Column(name = "RIGHT") @Enumerated(EnumType.ORDINAL) private Right right; 

但在这种情况下,JPA映射枚举索引(0,1,2)而不是我想要的值(100,200,300)。

我发现两个解决scheme似乎并不简单…

第一个scheme

这里提出的解决scheme使用@PrePersist和@PostLoad将枚举转换为其他字段,并将枚举字段标记为瞬态:

 @Basic private int intValueForAnEnum; @PrePersist void populateDBFields() { intValueForAnEnum = right.getValue(); } @PostLoad void populateTransientFields() { right = Right.valueOf(intValueForAnEnum); } 

第二个scheme

这里提出的第二个解决scheme提出了一个通用的转换对象,但仍然看起来很重和面向hibernate(@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") } ) 

有没有其他解决scheme?

我有几个想法,但我不知道它们是否存在于JPA中:

  • 在加载和保存Authority对象时,使用Authority Class右侧成员的setter和getter方法
  • 一个等同的想法是告诉JPA什么是右枚举的方法将枚举转换为int和int枚举
  • 因为我使用的是Spring,有没有办法告诉JPA使用特定的转换器(RightEditor)?

对于早于JPA 2.1的版本,JPA只提供两种方法来处理枚举,按name或按ordinal 。 而标准的JPA不支持自定义types。 所以:

  • 如果你想做自定义types转换,你将不得不使用一个提供商扩展(与Hibernate UserType ,EclipseLink Converter等)。 (第二个解决scheme)。 〜或〜
  • 你必须使用@PrePersist和@PostLoad技巧(第一个解决scheme)。 〜或〜
  • 注释getter和setter获取并返回int值〜或〜
  • 在实体级使用整数属性,并在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; 

更多细节:

最好的方法是将唯一的ID映射到每个枚举types,从而避免ORDINAL和STRING的陷阱。 看到这篇文章 ,其中概述了5种方法可以映射枚举。

从上面的链接采取:

1&2。 使用@Enumerated

目前有两种方法可以使用@Enumerated注释映射JPA实体中的枚举。 不幸的是EnumType.STRING和EnumType.ORDINAL都有其局限性。

如果使用EnumType.String,则重命名其中一个枚举types将导致枚举值与保存在数据库中的值不同步。 如果使用EnumType.ORDINAL,那么删除或重新sorting枚举中的types将导致保存在数据库中的值映射到错误的枚举types。

这两个选项都很脆弱。 如果在不执行数据库迁移的情况下修改枚举,则可能会损害数据的完整性。

3.生命周期callback

可能的解决scheme是使用JPA生命周期callback注释@PrePersist和@PostLoad。 这感觉相当难看,因为你现在在你的实体中有两个variables。 一个映射存储在数据库中的值,另一个映射实际的枚举值。

4.将唯一ID映射到每个枚举types

首选的解决scheme是将枚举映射到枚举中定义的固定值或ID。 映射到预定义的固定值使您的代码更加健壮。 对枚举types的顺序进行任何修改,或对名称进行重构都不会造成任何负面影响。

5.使用Java EE7 @Convert

如果您使用的是JPA 2.1,则可以select使用新的@Convert注释。 这需要创build一个转换器类,用@Converter注解,在这个类中你可以为每个枚举types定义保存到数据库中的值。 在你的实体中,你会用@Convert注释你的枚举。

我的偏好:(数字4)

我之所以喜欢在枚举中定义我的ID是为了反对使用转换器,是很好的封装。 只有枚举types应该知道它的ID,只有实体应该知道如何将枚举映射到数据库。

查看代码示例的原始post 。

问题是,我认为,JPA从来没有考虑到这个想法,我们可能已经有了一个复杂的已经存在的Schema。

我认为这有两个主要缺点,具体到Enum:

  1. 使用name()和ordinal()的限制。 为什么不用@Id标记一个getter,就像我们用@Entity做的那样?
  2. Enum在数据库中通常具有表示forms,以允许与各种元数据关联,包括专有名称,描述性名称,可能与本地化等有关的内容。我们需要将Enum与实体的灵活性结合起来使用。

帮助我的事业并对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; } } 

从JPA 2.1你可以使用AttributeConverter 。

像这样创build一个枚举类:

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

并创build一个这样的转换器:

 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 : values()) { if (nodeType.getCode().equals(dbData)) { return nodeType; } } throw new IllegalArgumentException("Unknown database value:" + dbData); } } 

在你只需要的实体上:

 @Column(name = "node_type_code") 

你运气@Converter(autoApply = true)可能因容器而异,但经过testing可以在Wildfly 8.1.0上运行。 如果不起作用,可以在实体类列上添加@Convert(converter = NodeTypeConverter.class)

可能密切相关的帕斯卡代码

 @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 Etapa convertToEntityAttribute(Integer dbData) { return Etapa.valueOf(dbData); } } 

pipe理局实体:

 @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 

我觉得这很酷。 这是不完全正确的,因为转换器不打算与enum的使用。 实际上,文档说永远不会为此使用它,您应该使用@Enumerated注释。 问题是只有两个枚举types: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; } } 

和pipe理局实体

 @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,其中enum值字段应该用某种@EnumId注释来注释。