我正在寻找使用JPA映射枚举的不同方法。我特别想设置每个enum条目的整数值,并且只保存整数值。

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

一个简单的解决方案是将枚举注释与EnumType一起使用。顺序:

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

但是在本例中,JPA映射的是枚举索引(0,1,2),而不是我想要的值(100,200,300)。

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

第一个解决方案

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

@Basic
private int intValueForAnEnum;

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

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

第二个解决方案

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

还有其他解决方案吗?

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

在加载和保存Authority对象时,使用Authority类的right成员的setter和getter方法 一个等效的想法是告诉JPA什么是右枚举的方法来将enum转换为int和int转换为enum 因为我使用Spring,有任何方法告诉JPA使用特定的转换器(理义)吗?


当前回答

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

摘自上面的链接:

1、2点。使用@Enumerated

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

如果你使用EnumType。字符串,然后重命名枚举类型之一将导致枚举值与保存在数据库中的值不同步。如果你使用EnumType。然后删除或重新排序枚举中的类型将导致数据库中保存的值映射到错误的枚举类型。

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

3.生命周期回调

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

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

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

5. 使用Java EE7 @Convert

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

我的偏好:(4)

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

有关代码示例,请参阅原文。

其他回答

问题是,我认为,JPA在开始时从来没有想到我们可以有一个已经存在的复杂Schema。

我认为这导致了两个主要缺点,特别是Enum:

使用name()和ordinal()的限制。为什么不像@Entity那样用@Id标记getter呢? 枚举通常在数据库中有表示,以允许与各种元数据关联,包括专有名称,描述性名称,可能还有本地化等。我们需要易于使用的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;
  }
}

可能是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;

}

通过这种方式,您不能直接设置为enum字段。但是,您可以在“权限”中使用

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

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

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

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


然而,如果它不能让你满意,你可以做一些更简单一点的东西。

让´s看看。

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注释。

我自己解决这种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 -让原始enum实现我们在步骤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 enum扩展步骤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(你也可以在你的实体中这样做,以避免创建一个分离的类); 用@Convert from javax注解enum属性。持久性方案。

希望这能有所帮助。

在JPA 2.1中,您可以使用attributecconverter。

像这样创建一个枚举类:

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)。