web-dev-qa-db-ja.com

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

簡単な解決策は、EnumType.ORDINALでEnumeratedアノテーションを使用することです。

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

しかし、この場合、JPAは列挙インデックス(0,1,2)をマップし、必要な値(100,200,300)ではありません。

私が見つけた2つの解決策は簡単ではないようです...

最初の解決策

解決策 ここで提案 は、@ PrePersistと@PostLoadを使用して列挙型を他のフィールドに変換し、列挙型フィールドを一時的なものとしてマークします。

@Basic
private int intValueForAnEnum;

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

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

第二の解決策

2番目の解決策 ここで提案 は一般的な変換オブジェクトを提案しましたが、それでも重く、休止状態指向です(@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クラスの正しいメンバーのsetterおよびgetterメソッドを使用します
  • 同等のアイデアは、JPAにenumをintに、intをenumに変換するRight enumのメソッドを伝えることです
  • Springを使用しているため、JPAに特定のコンバーター(RightEditor)を使用するように指示する方法はありますか?
178
Kartoch

JPA 2.1より前のバージョンの場合、JPAはnameまたはordinalによって列挙型を処理する2つの方法のみを提供します。また、標準のJPAはカスタムタイプをサポートしていません。そう:

  • カスタム型変換を行う場合は、プロバイダー拡張機能(Hibernate UserType、EclipseLink Converterなど)を使用する必要があります。 (2番目の解決策)。 〜または〜
  • @PrePersistおよび@PostLoadトリックを使用する必要があります(最初のソリューション)。 〜または〜
  • int値を取得して返すゲッターとセッターに注釈を付ける〜または〜
  • エンティティレベルで整数属性を使用し、ゲッターとセッターで変換を実行します。

最新のオプションを説明します(これは基本的な実装です。必要に応じて調整してください)。

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

}
163
Pascal Thivent

これは、JPA 2.1で可能になりました。

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

詳細:

63
Tvaroh

最善のアプローチは、一意のIDを各列挙型にマップすることで、ORDINALとSTRINGの落とし穴を回避することです。これを参照してください post これは、列挙型をマップできる5つの方法の概要を示しています。

上記のリンクから取得:

1&2。 @Enumeratedの使用

現在、@ Enumeratedアノテーションを使用して、JPAエンティティ内で列挙型をマッピングする方法は2つあります。残念ながら、EnumType.STRINGとEnumType.ORDINALの両方に制限があります。

EnumType.Stringを使用する場合、列挙型のいずれかの名前を変更すると、列挙値がデータベースに保存されている値と同期しなくなります。 EnumType.ORDINALを使用する場合、列挙内の型を削除または並べ替えると、データベースに保存されている値が間違った列挙型にマップされます。

これらのオプションはどちらも脆弱です。データベースの移行を実行せずに列挙型を変更すると、データの整合性が損なわれる可能性があります。

3。ライフサイクルコールバック

考えられる解決策は、JPAライフサイクルコールバックアノテーション@PrePersistおよび@PostLoadを使用することです。エンティティに2つの変数があるため、これは非常にい感じがします。 1つはデータベースに格納された値をマッピングし、もう1つは実際の列挙をマッピングします。

4。一意のIDを各列挙型にマッピングする

推奨される解決策は、列挙内で定義された固定値またはIDに列挙をマップすることです。定義済みの固定値にマッピングすると、コードがより堅牢になります。列挙型の順序の変更、または名前のリファクタリングは、悪影響を引き起こしません。

5。 Java EE7 @Convertを使用する

JPA 2.1を使用している場合、新しい@Convertアノテーションを使用するオプションがあります。これには、@ Converterアノテーションが付けられたコンバータークラスを作成する必要があります。このクラス内では、各列挙型に対してデータベースに保存する値を定義します。エンティティ内で、enumに@Convertアノテーションを付けます。

私の好み:(数4)

コンバーターの使用とは反対に列挙内でIDを定義することを好む理由は、適切なカプセル化です。列挙型のみがそのIDを知る必要があり、エンティティのみが列挙型をデータベースにマップする方法を知る必要があります。

コード例については、元の post を参照してください。

16
Chris Ritchie

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

そして、次のようなコンバーターを作成します。

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)を追加できます。

15
Pool

問題は、複雑な既存のスキーマをすでに配置できるという考えを念頭に置いて、JPAが決して受け入れられなかったことだと思います。

これにより、Enumに固有の2つの主な欠点があると思います。

  1. Name()およびordinal()の使用の制限。なぜ@Entityで行うように、@ Idでゲッターをマークしないのですか?
  2. Enumは通常、データベース内に表現を持ち、適切な名前、説明的な名前、ローカライズされた名前など、あらゆる種類のメタデータとの関連付けを可能にします。Entityの柔軟性と組み合わせた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;
  }
}
10
YoYo

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

}
3
Rafiq

私は次のことをします:

独自のファイルで列挙を個別に宣言します。

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

}

Authorityエンティティ:

@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フィールドに直接設定できません。ただし、AuthorityでRightフィールドを設定することはできます

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

比較する必要がある場合は、次を使用できます。

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

これはかなりクールだと思います。コンバーターは列挙型で使用することを意図していないため、完全に正しいわけではありません。実際には、ドキュメントではこの目的に使用しないと記載されているため、代わりに@Enumeratedアノテーションを使用する必要があります。問題は、2つの列挙型(ORDINALまたはSTRING)しかありませんが、ORDINALは扱いにくく安全ではないことです。


しかし、それがあなたを満足させないなら、あなたはもう少しハック的で簡単なことをすることができます(またはそうではありません)。

どれどれ。

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;

}

この2番目のアイデアでは、順序属性をハックするので完全な状況ではありませんが、はるかに小さいコーディングです。

JPA仕様には、列挙値フィールドに何らかの種類の@EnumIdアノテーションを付ける必要があるEnumType.IDを含める必要があると思います。

1
Carlos Cariello