web-dev-qa-db-ja.com

列挙型のJPAマップコレクション

JPAにEntityクラス内のEnumsのコレクションをマップする方法はありますか?または、唯一の解決策は、Enumを別のドメインクラスでラップし、それを使用してコレクションをマップすることですか?

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

私はHibernate JPA実装を使用していますが、もちろん実装に依存しないソリューションを好みます。

79

あなたができるHibernateを使用して

@CollectionOfElements(targetElement = InterestsEnum.class)
@JoinTable(name = "tblInterests", joinColumns = @JoinColumn(name = "personID"))
@Column(name = "interest", nullable = false)
@Enumerated(EnumType.STRING)
Collection<InterestsEnum> interests;
104
ant

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

この簡単な方法でこれを達成できました。

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

here で説明されている遅延読み込みの初期化エラーを回避するために、積極的な読み込みが必要です。

6
megalucio

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_を参照)
  • セットはデータベース内の1つの数値列です
  • すべてがプレーンJPAです(プロバイダー固有ではありませんカスタムタイプ
  • 他のソリューションと比較して、同じタイプの新しいフィールドの簡単な(そして短い)宣言

欠点:

  • コードの重複(RegularEnumSetPersistentEnumSetはほぼ同じです)
    • EnumSet.noneOf(enumType)の結果をPersistenEnumSetでラップし、_AccessType.PROPERTY_を宣言し、リフレクションを使用してelementsフィールドを読み書きする2つのアクセス方法を提供できます。
  • 永続セットに格納する必要のあるすべての列挙クラスには、追加のセットクラスが必要です。
    • 永続プロバイダーがパブリックコンストラクターなしで埋め込み可能をサポートしている場合は、PersistentEnumSetに_@Embeddable_を追加し、余分なクラス(... interests = new PersistentEnumSet<>(InterestsEnum.class);)を削除できます。
  • エンティティに複数のPersistentEnumSetがある場合は、私の例にあるように_@AttributeOverride_を使用する必要があります(そうしないと、両方が同じ列「要素」に格納されます)
  • コンストラクターでのリフレクションによるvalues()のアクセスは最適ではありません(特にパフォーマンスを見る場合)が、他の2つのオプションにも欠点があります:
    • EnumSet.getUniverse()のような実装は_Sun.misc_クラスを利用します
    • パラメータとして値の配列を提供すると、指定された値が正しい値ではないというリスクがあります
  • 最大64個の値を持つ列挙のみがサポートされます(これは本当に欠点ですか?)
    • 代わりにBigIntegerを使用できます
  • 条件クエリまたはJPQL でelementsフィールドを使用するのは簡単ではありません
    • データベースでサポートされている場合は、適切な関数でバイナリ演算子またはビットマスク列を使用できます
5
Tobias Liefke

JPAのコレクションは、1対多または多対多の関係を参照し、他のエンティティのみを含むことができます。申し訳ありませんが、これらの列挙型をエンティティでラップする必要があります。考えてみると、とにかくこの情報を保存するために何らかのIDフィールドと外部キーが必要になります。これは、文字列にコンマ区切りのリストを保存するなどのクレイジーなことをしない限りです(これはしないでください!)。

0
cletus