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

Kar*_*och 180 java orm enums spring 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;
}
Run Code Online (Sandbox Code Playgroud)

一个简单的解决方案是使用EnumType注释和EnumType.ORDINAL:

@Column(name = "RIGHT")
@Enumerated(EnumType.ORDINAL)
private Right right;
Run Code Online (Sandbox Code Playgroud)

但在这种情况下,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);
}
Run Code Online (Sandbox Code Playgroud)

二解决方案

这里提出的第二个解决方案提出了一个通用的转换对象,但仍然看起来很重,并且面向hibernate(Java EE中似乎不存在@Type):

@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")
            }
)
Run Code Online (Sandbox Code Playgroud)

还有其他解决方案吗?

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

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

Pas*_*ent 163

对于早于JPA 2.1的版本,JPA只提供两种方式来处理枚举,由它们name或由它们来处理ordinal.标准JPA不支持自定义类型.所以:

  • 如果要进行自定义类型转换,则必须使用提供程序扩展(使用Hibernate UserType,EclipseLink Converter等).(第二种解决方案).〜或〜
  • 你必须使用@PrePersist和@PostLoad技巧(第一个解决方案).〜或〜
  • 注释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();
    }

}
Run Code Online (Sandbox Code Playgroud)

  • 令人伤心的JPA没有原生支持 (49认同)
  • @jaime同意!认为开发人员可能希望将枚举作为其某个字段/属性的值而不是其int值或名称来保留,这是不是很疯狂?这两者都非常"脆弱"和重构 - 不友好.使用该名称还假定您在Java和数据库中使用相同的命名约定.以性别为例.这可能在数据库中被简单地定义为"M"或"F",但这不应该阻止我在Java中使用Gender.MALE和Gender.FEMALE,而不是Gender.M或Gender.F. (19认同)
  • JPA2.1本身将具有转换器支持.请参阅http://somethoughtsonjava.blogspot.fi/2013/10/jpa-21-type-converter-better-way-to.html (13认同)
  • 我想一个原因可能是名称和序数都保证是唯一的,而任何其他值或字段都不是.确实,订单可以改变(不要使用序数),名称也可以改变(不要改变枚举名称:P),但任何其他价值都可以改变......我不确定我是否看到添加存储不同值的功能的巨大价值. (2认同)
  • 实际上,我确实看到了价值......如果我仍然可以编辑它,我会删除上面评论的那部分:P:D (2认同)

Tva*_*roh 63

现在可以使用JPA 2.1:

@Column(name = "RIGHT")
@Enumerated(EnumType.STRING)
private Right right;
Run Code Online (Sandbox Code Playgroud)

更多详情:

  • “答案”引用了2条有关使用“ AttributeConverter”的链接,但BUT引用了一些不执行任何操作且不回答OP的代码。 (4认同)
  • 现在有什么可能?当然我们可以使用 `@Converter`,但是 `enum` 应该开箱即用地处理得更优雅! (2认同)
  • 添加后完美工作:`@Convert(converter = Converter.class) @Enumerated(EnumType.STRING)` (2认同)

Poo*_*ool 16

从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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

在您需要的实体上:

@Column(name = "node_type_code")
Run Code Online (Sandbox Code Playgroud)

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


Chr*_*hie 16

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

取自上面的链接:

1&2.使用@Enumerated

目前有两种方法可以使用@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号)

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

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


YoY*_*oYo 10

问题是,我认为,JPA从未接受过这样的想法,即我们可能已经有一个复杂的预先存在的Schema.

我认为这有两个主要的缺点,特别是Enum:

  1. 使用name()和ordinal()的限制.为什么不用@Id标记一个getter,就像我们使用@Entity一样?
  2. Enum通常在数据库中表示允许与各种元数据相关联,包括正确的名称,描述性名称,可能具有本地化的东西等.我们需要易于使用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;
  }
}
Run Code Online (Sandbox Code Playgroud)


Dan*_*lli 5

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

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

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

    T getDbVal();

}
Run Code Online (Sandbox Code Playgroud)

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

}
Run Code Online (Sandbox Code Playgroud)

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

步骤 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

public class RightConverter extends EnumDbValueConverter<Integer, Right> {
    public RightConverter() {
        super(Right.class);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

@Column(name = "RIGHT")
@Convert(converter = RightConverter.class)
private Right right;
Run Code Online (Sandbox Code Playgroud)

结论

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

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

  • IDbValue在您的枚举上实现接口;
  • EnumDbValueConverter仅用 3 行代码扩展(您也可以在实体中执行此操作以避免创建单独的类);
  • 使用@Convertfromjavax.persistence包注释 enum 属性。

希望这可以帮助。