枚举的 JPA 地图集合

IT小君   2021-10-16T04:27:00

JPA 中有没有办法映射实体类中的枚举集合?或者唯一的解决方案是用另一个域类包装 Enum 并使用它来映射集合?

@Entity
public class Person {
    public enum InterestsEnum {Books, Sport, etc...  }
    //@???
    Collection<InterestsEnum> interests;
}

我正在使用 Hibernate JPA 实现,但当然更喜欢实现不可知的解决方案。

评论(6)
IT小君

使用 Hibernate 你可以做

@CollectionOfElements(targetElement = InterestsEnum.class)
@JoinTable(name = "tblInterests", joinColumns = @JoinColumn(name = "personID"))
@Column(name = "interest", nullable = false)
@Enumerated(EnumType.STRING)
Collection<InterestsEnum> interests;
2021-10-16T04:27:01   回复
IT小君

Andy 的回答中的链接是在 JPA 2 中映射“非实体”对象集合的一个很好的起点,但在映射枚举时并不完整。这是我想出来的。

@Entity
public class Person {
    @ElementCollection(targetClass=InterestsEnum.class)
    @Enumerated(EnumType.STRING) // Possibly optional (I'm not sure) but defaults to ORDINAL.
    @CollectionTable(name="person_interest")
    @Column(name="interest") // Column name in person_interest
    Collection<InterestsEnum> interests;
}
2021-10-16T04:27:01   回复
IT小君

我能够以这种简单的方式完成此操作:

@ElementCollection(fetch = FetchType.EAGER)
Collection<InterestsEnum> interests;

预先加载需要,以避免延迟加载inizializing错误的解释在这里

2021-10-16T04:27:01   回复
IT小君

tl;dr 一个简短的解决方案如下:

@ElementCollection(targetClass = InterestsEnum.class)
@CollectionTable
@Enumerated(EnumType.STRING)
Collection<InterestsEnum> interests;

长的答案是,使用此注释,JPA 将创建一个表,该表将保存指向主类标识符(在本例中为 Person.class)的 InterestsEnum 列表。

@ElementCollections 指定 JPA 可以在何处找到有关 Enum 的信息

@CollectionTable 创建保存从 Person 到 InterestsEnum 的关系的表

@Enumerated(EnumType.STRING) 告诉 JPA 将枚举作为字符串持久化,可以是 EnumType.ORDINAL

2021-10-16T04:27:01   回复
IT小君

我正在使用对 java.util.RegularEnumSet 的轻微修改来获得持久的 EnumSet:

@MappedSuperclass
@Access(AccessType.FIELD)
public class PersistentEnumSet<E extends Enum<E>> 
    extends AbstractSet<E> {
  private long elements;

  @Transient
  private final Class<E> elementType;

  @Transient
  private final E[] universe;

  public PersistentEnumSet(final Class<E> elementType) {
    this.elementType = elementType;
    try {
      this.universe = (E[]) elementType.getMethod("values").invoke(null);
    } catch (final ReflectiveOperationException e) {
      throw new IllegalArgumentException("Not an enum type: " + elementType, e);
    }
    if (this.universe.length > 64) {
      throw new IllegalArgumentException("More than 64 enum elements are not allowed");
    }
  }

  // Copy everything else from java.util.RegularEnumSet
  // ...
}

这个类现在是我所有枚举集的基础:

@Embeddable
public class InterestsSet extends PersistentEnumSet<InterestsEnum> {
  public InterestsSet() {
    super(InterestsEnum.class);
  }
}

我可以在我的实体中使用该集合:

@Entity
public class MyEntity {
  // ...
  @Embedded
  @AttributeOverride(name="elements", column=@Column(name="interests"))
  private InterestsSet interests = new InterestsSet();
}

好处:

  • 使用代码中设置的类型安全和高性能枚举(请参阅java.util.EnumSet有关说明)
  • 该集合只是数据库中的一个数字列
  • 一切都是普通的 JPA(没有提供者特定的自定义类型
  • 与其他解决方案相比,易于(且简短)声明相同类型的新字段

缺点:

  • 代码重复(RegularEnumSet并且PersistentEnumSet几乎相同)
    • 您可以将 的结果包装EnumSet.noneOf(enumType)在您的PersistenEnumSet,声明AccessType.PROPERTY并提供两种使用反射来读取和写入elements字段的访问方法
  • 每个应该存储在持久集合中的枚举类都需要一个额外的集合类
    • 如果你的持久提供商支持embeddables没有公共构造函数,你可以添加@EmbeddablePersistentEnumSet并删除多余的类(... interests = new PersistentEnumSet<>(InterestsEnum.class);
  • @AttributeOverride如果PersistentEnumSet实体中有多个,则必须使用,如我的示例中所示(否则两者都将存储到同一列“元素”中)
  • values()构造函数中with 反射访问不是最佳的(尤其是在查看性能时),但其他两个选项也有其缺点:
    • 类似的EnumSet.getUniverse()实现使用了一个sun.misc
    • 提供 values 数组作为参数有给定值不是正确值的风险
  • 仅支持最多 64 个值的枚举(这真的是一个缺点吗?)
    • 你可以改用 BigInteger
  • 在标准查询或 JPQL 中使用元素字段并不容易
    • 如果您的数据库支持,您可以使用具有适当功能的二元运算符或位掩码列
2021-10-16T04:27:01   回复
IT小君

JPA 中的集合是指一对多或多对多的关系,它们只能包含其他实体。抱歉,您需要将这些枚举包装在一个实体中。如果您考虑一下,无论如何您都需要某种 ID 字段和外键来存储此信息。除非你做一些疯狂的事情,比如在字符串中存储一个逗号分隔的列表(不要这样做!)。

2021-10-16T04:27:01   回复