Java 使用固定值映射 JPA 中的枚举?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2751733/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 12:27:41  来源:igfitidea点击:

Map enum in JPA with fixed values?

javaspringormjpaenums

提问by Kartoch

I'm looking for the different ways to map an enum using JPA. I especially want to set the integer value of each enum entry and to save only the integer value.

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

A simple solution is to use the Enumerated annotation with EnumType.ORDINAL:

一个简单的解决方案是将 Enumerated 注释与 EnumType.ORDINAL 一起使用:

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

But in this case JPA maps the enum index (0,1,2) and not the value I want (100,200,300).

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

Th two solutions I found do not seem simple...

我发现的两个解决方案似乎并不简单......

First Solution

第一个解决方案

A solution, proposed here, uses @PrePersist and @PostLoad to convert the enum to an other field and mark the enum field as transient:

此处提出的解决方案使用@PrePersist 和@PostLoad 将枚举转换为其他字段并将枚举字段标记为瞬态:

@Basic
private int intValueForAnEnum;

@PrePersist
void populateDBFields() {
  intValueForAnEnum = right.getValue();
}

@PostLoad
void populateTransientFields() {
  right = Right.valueOf(intValueForAnEnum);
}

Second Solution

第二种解决方案

The second solution proposed hereproposed a generic conversion object, but still seems heavy and hibernate-oriented (@Type doesn't seem to exist in Java EE):

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

Is there any other solutions ?

还有其他解决方案吗?

I've several ideas in mind but I don't know if they exist in JPA:

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

  • use the setter and getter methods of right member of Authority Class when loading and saving the Authority object
  • an equivalent idea would be to tell JPA what are the methods of Right enum to convert enum to int and int to enum
  • Because I'm using Spring, is there any way to tell JPA to use a specific converter (RightEditor) ?
  • 加载和保存Authority对象时,使用Authority类的right成员的setter和getter方法
  • 一个等效的想法是告诉 JPA Right enum 将 enum 转换为 int 和 int 到 enum 的方法是什么
  • 因为我使用的是 Spring,所以有什么方法可以告诉 JPA 使用特定的转换器 (RightEditor) 吗?

采纳答案by Pascal Thivent

For versions earlier than JPA 2.1, JPA provides only two ways to deal with enums, by their nameor by their ordinal. And the standard JPA doesn't support custom types. So:

对于早于 JPA 2.1 的版本,JPA 仅提供两种处理枚举的方法,通过其name或通过其ordinal. 并且标准 JPA 不支持自定义类型。所以:

  • If you want to do custom type conversions, you'll have to use a provider extension (with Hibernate UserType, EclipseLink Converter, etc). (the second solution). ~or~
  • You'll have to use the @PrePersist and @PostLoad trick (the first solution). ~or~
  • Annotate getter and setter taking and returning the intvalue ~or~
  • Use an integer attribute at the entity level and perform a translation in getters and setters.
  • 如果要进行自定义类型转换,则必须使用提供程序扩展(使用 Hibernate UserType、 EclipseLinkConverter等)。(第二种解决方案)。~或~
  • 您必须使用 @PrePersist 和 @PostLoad 技巧(第一个解决方案)。~或~
  • 注释 getter 和 setter 获取并返回int值~或~
  • 在实体级别使用整数属性并在 getter 和 setter 中执行转换。

I'll illustrate the latest option (this is a basic implementation, tweak it as required):

我将说明最新的选项(这是一个基本实现,根据需要进行调整):

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

}

回答by Rafiq

Possibly close related code of Pascal

可能与 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();
    }

}

回答by YoYo

The problem is, I think, that JPA was never incepted with the idea in mind that we could have a complex preexisting Schema already in place.

我认为问题在于,JPA 从未考虑过我们可以拥有一个复杂的预先存在的架构的想法。

I think there are two main shortcomings resulting from this, specific to Enum:

我认为这有两个主要缺点,特定于 Enum:

  1. The limitation of using name() and ordinal(). Why not just mark a getter with @Id, the way we do with @Entity?
  2. Enum's have usually representation in the database to allow association with all sorts of metadata, including a proper name, a descriptive name, maybe something with localization etc. We need the easy of use of an Enum combined with the flexibility of an Entity.
  1. 使用 name() 和 ordinal() 的限制。为什么不直接用@Id 标记一个getter,就像我们用@Entity 做的那样?
  2. 枚举通常在数据库中具有表示,以允许与各种元数据相关联,包括专有名称、描述性名称、可能具有本地化功能等。我们需要枚举的易用性与实体的灵活性相结合。

Help my cause and vote on JPA_SPEC-47

帮助我的事业并在JPA_SPEC-47上投票

Would this not be more elegant than using a @Converter to solve the problem?

这不会比使用@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;
  }
}

回答by Tvaroh

This is now possible with JPA 2.1:

现在可以使用 JPA 2.1:

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

Further details:

更多细节:

回答by Pool

From JPA 2.1 you can use AttributeConverter.

从 JPA 2.1 开始,您可以使用AttributeConverter

Create an enumerated class like so:

创建一个枚举类,如下所示:

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

And create a converter like this:

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

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

On the entity you just need:

在您只需要的实体上:

@Column(name = "node_type_code")

You luck with @Converter(autoApply = true)may vary by container but tested to work on Wildfly 8.1.0. If it doesn't work you can add @Convert(converter = NodeTypeConverter.class)on the entity class column.

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

回答by Carlos Cariello

I would do the folowing:

我会做以下:

Declare separetly the enum, in it′s own file:

在它自己的文件中单独声明枚举:

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


}

Declare a new JPA entity named Right

声明一个名为 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);
    }


}

You will also need a converter for receiving this values (JPA 2.1 only and there′s a problem I′ll not discuss here with these enum′s to be directly persisted using the converter, so it will be a one way road only)

您还需要一个转换器来接收这些值(仅限 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);
    }

}

The Authority entity:

管理局实体:

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

}

By doing this way, you can′t set directly to the enum field. However, you can set the Right field in Authority using

通过这种方式,您不能直接设置为枚举字段。但是,您可以使用设置权限中的权限字段

autorithy.setRight( Right.getInstance( RightEnum.READ ) );//for example

And if you need to compare, you can use:

如果需要比较,可以使用:

authority.getRight().equals( RightEnum.READ ); //for example

Which is pretty cool, I think. It′s not totally correct, since the converter it′s not intended to be use with enum′s. Actually, the documentation says to never use it for this purpose, you should use the @Enumerated annotation instead. The problem is that there are only two enum types: ORDINAL or STRING, but the ORDINAL is tricky and not safe.

我觉得这很酷。这并不完全正确,因为转换器不打算与枚举一起使用。实际上,文档说永远不要将它用于此目的,您应该使用 @Enumerated 批注。问题是只有两种枚举类型:ORDINAL 或 STRING,但 ORDINAL 很棘手且不安全。



However, if it doesn′t satisfy you, you can do something a little more hacky and simpler (or not).

但是,如果它不满足您,您可以做一些更hacky 和更简单(或不)的事情。

Let′s see.

让我们来看看。

The RightEnum:

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


}

and the Authority entity

和管理局实体

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

}

In this second idea, its not a perfect situation since we hack the ordinal attribute, but it′s a much smaller coding.

在这第二个想法中,它不是一个完美的情况,因为我们破解了序数属性,但它的编码要小得多。

I think that the JPA specification should include the EnumType.ID where the enum value field should be annotated with some kind of @EnumId annotation.

我认为 JPA 规范应该包括 EnumType.ID,其中枚举值字段应该用某种 @EnumId 注释进行注释。

回答by Chris Ritchie

The best approach would be to map a unique ID to each enum type, thus avoiding the pitfalls of ORDINAL and STRING. See this postwhich outlines 5 ways you can map an enum.

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

Taken from the link above:

取自上面的链接:

1&2. Using @Enumerated

1&2。使用@Enumerated

There are currently 2 ways you can map enums within your JPA entities using the @Enumerated annotation. Unfortunately both EnumType.STRING and EnumType.ORDINAL have their limitations.

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

If you use EnumType.String then renaming one of your enum types will cause your enum value to be out of sync with the values saved in the database. If you use EnumType.ORDINAL then deleting or reordering the types within your enum will cause the values saved in the database to map to the wrong enums types.

如果您使用 EnumType.String 则重命名您的枚举类型之一将导致您的枚举值与保存在数据库中的值不同步。如果您使用 EnumType.ORDINAL,则删除或重新排序枚举中的类型将导致保存在数据库中的值映射到错误的枚举类型。

Both of these options are fragile. If the enum is modified without performing a database migration, you could jeopodise the integrity of your data.

这两种选择都很脆弱。如果在不执行数据库迁移的情况下修改了枚举,则可能会危及数据的完整性。

3. Lifecycle Callbacks

3. 生命周期回调

A possible solution would to use the JPA lifecycle call back annotations, @PrePersist and @PostLoad. This feels quite ugly as you will now have two variables in your entity. One mapping the value stored in the database, and the other, the actual enum.

一种可能的解决方案是使用 JPA 生命周期回调注释、@PrePersist 和 @PostLoad。这感觉非常难看,因为您的实体中现在将有两个变量。一个映射存储在数据库中的值,另一个映射实际的枚举。

4. Mapping unique ID to each enum type

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

The preferred solution is to map your enum to a fixed value, or ID, defined within the enum. Mapping to predefined, fixed value makes your code more robust. Any modification to the order of the enums types, or the refactoring of the names, will not cause any adverse effects.

首选的解决方案是将枚举映射到枚举中定义的固定值或 ID。映射到预定义的固定值使您的代码更加健壮。对枚举类型顺序的任何修改或名称的重构都不会造成任何不利影响。

5. Using Java EE7 @Convert

5. 使用 Java EE7 @Convert

If you are using JPA 2.1 you have the option to use the new @Convert annotation. This requires the creation of a converter class, annotated with @Converter, inside which you would define what values are saved into the database for each enum type. Within your entity you would then annotate your enum with @Convert.

如果您使用的是 JPA 2.1,您可以选择使用新的 @Convert 注释。这需要创建一个转换器类,用@Converter 进行注释,您可以在其中定义为每个枚举类型将哪些值保存到数据库中。在您的实体中,您将使用@Convert 注释您的枚举。

My preference: (Number 4)

我的偏好:(4号)

The reason why I prefer to define my ID's within the enum as oppose to using a converter, is good encapsulation. Only the enum type should know of its ID, and only the entity should know about how it maps the enum to the database.

我更喜欢在枚举中定义我的 ID 来反对使用转换器的原因是良好的封装。只有枚举类型应该知道它的 ID,并且只有实体应该知道它如何将枚举映射到数据库。

See the original postfor the code example.

有关代码示例,请参阅原始帖子

回答by Danilo Cianciulli

My own solution to solve this kind of Enum JPA mapping is the following.

我自己解决这种 Enum JPA 映射的解决方案如下。

Step 1- Write the following interface that we will use for all enums that we want to map to a db column:

步骤 1- 编写以下接口,我们将用于所有要映射到 db 列的枚举:

public interface IDbValue<T extends java.io.Serializable> {

    T getDbVal();

}

Step 2- Implement a custom generic JPA converter as follows:

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

}

This class will convert the enum value Eto a database field of type T(e.g. String) by using the getDbVal()on enum E, and vice versa.

此类将使用on enum将枚举值转换E为类型T(例如String)的数据库字段,反之亦然。getDbVal()E

Step 3- Let the original enum implement the interface we defined in step 1:

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

Step 4- Extend the converter of step 2 for the Rightenum of step 3:

第 4 步-Right为第 3步的枚举扩展第 2 步的转换器:

public class RightConverter extends EnumDbValueConverter<Integer, Right> {
    public RightConverter() {
        super(Right.class);
    }
}

Step 5- The final step is to annotate the field in the entity as follows:

第 5 步- 最后一步是对实体中的字段进行注释,如下所示:

@Column(name = "RIGHT")
@Convert(converter = RightConverter.class)
private Right right;

Conclusion

结论

IMHO this is the cleanest and most elegant solution if you have many enums to map and you want to use a particular field of the enum itself as mapping value.

恕我直言,如果您有许多要映射的枚举并且想要使用枚举本身的特定字段作为映射值,那么这是最干净、最优雅的解决方案。

For all others enums in your project that need similar mapping logic, you only have to repeat steps 3 to 5, that is:

对于您项目中需要类似映射逻辑的所有其他枚举,您只需重复步骤 3 到 5,即:

  • implement the interface IDbValueon your enum;
  • extend the EnumDbValueConverterwith only 3 lines of code (you may also do this within your entity to avoid creating a separated class);
  • annotate the enum attribute with @Convertfrom javax.persistencepackage.
  • IDbValue在您的枚举上实现接口;
  • EnumDbValueConverter仅用 3 行代码扩展(您也可以在实体中执行此操作以避免创建单独的类);
  • 使用@Convertfromjavax.persistence包注释 enum 属性。

Hope this helps.

希望这可以帮助。

回答by Mahdi Ben Selimene

public enum Gender{ 
    MALE, FEMALE 
}



@Entity
@Table( name="clienti" )
public class Cliente implements Serializable {
...

// **1 case** - If database column type is number (integer) 
// (some time for better search performance)  -> we should use 
// EnumType.ORDINAL as @O.Badr noticed. e.g. inserted number will
// index of constant starting from 0... in our example for MALE - 0, FEMALE - 1.
// **Possible issue (advice)**: you have to add the new values at the end of
// your enum, in order to keep the ordinal correct for future values.

@Enumerated(EnumType.ORDINAL)
    private Gender gender;


// **2 case** - If database column type is character (varchar) 
// and you want to save it as String constant then ->

@Enumerated(EnumType.STRING)
    private Gender gender;

...
}

// in all case on code level you will interact with defined 
// type of Enum constant but in Database level

first case (EnumType.ORDINAL)

第一种情况 ( EnumType.ORDINAL)

╔════╦══════════════╦════════╗
║ ID ║    NAME      ║ GENDER ║
╠════╬══════════════╬════════╣
║  1 ║ Jeff Atwood  ║    0   ║
║  2 ║ Geoff Dalgas ║    0   ║
║  3 ║Jarrod Jesica ║    1   ║
║  4 ║ Joel Lucy    ║    1   ║
╚════╩══════════════╩════════╝

second case (EnumType.STRING)

第二种情况 ( EnumType.STRING)

╔════╦══════════════╦════════╗
║ ID ║    NAME      ║ GENDER ║
╠════╬══════════════╬════════╣
║  1 ║ Jeff Atwood  ║  MALE  ║
║  2 ║ Geoff Dalgas ║  MALE  ║
║  3 ║Jarrod Jesica ║ FEMALE ║
║  4 ║ Joel Lucy    ║ FEMALE ║
╚════╩══════════════╩════════╝