JPA:データベースに列挙を保存する

ステータスやオブジェクトの種類など、さまざまな種類のサービスディレクトリを操作する便利な方法を実装しようとすると、データベースに転送を保存する問題が発生することは間違いありません。



その本質は非常に単純です:列挙をエンティティ( @Entity



)として保存すると、それらを操作するのは非常に不便であることが判明し、キャッシュにもかかわらずデータベースに追加のクエリがロードされ、データベースクエリ自体が追加のJOINによって複雑になります。 列挙が列挙として定義されている場合、それらを操作するのが便利になりますが、データベースとの同期およびそのような同期のエラーの追跡の問題があります。



これは、列挙を含むフィールドに@Enumerated(EnumType.ORDINAL)



として注釈が付けられている場合に特に当てはまります。値の宣言の順序を変更すると、すべてが即座に壊れます。 @Enumerated(EnumType.STRING)



ような文字列形式で値を保存する場合、文字列フィールドのインデックスは効率が悪く、より多くのスペースを占有するため、アクセス速度の問題があります。 さらに、データベースに有効な値のリストを含むテーブルがない場合にフィールド値がどのように保存されるかに関係なく、不正なデータや古いデータ、およびその結果の問題から保護されることはありません。



ただし、データベースに格納するという考えは、クエリを手動で作成する単純さによって魅力的です。これは、ソフトウェアのデバッグや複雑な状況の解決に非常に役立ちます。 SELECT id, title FROM product WHERE status = 5



でなく、たとえば、 SELECT id, title FROM product JOIN status ON status.id = product.status_id WHERE status.code = 'NEW'



書くことができる場合SELECT id, title FROM product WHERE status = 5



これは非常に貴重です。 FOREIGN KEY



を設定した場合、 status_id



に正しい値が含まれることを常に確認できるという事実を含めます。



実際、この問題には非常にシンプルでエレガントな解決策があります。それは、すべての鳥を一石で殺します。





このソリューションは、単純なハックに基づいています。これは、ハックではありますが、副作用はありません。 ご存知のように、Javaでの転送は単なる構文糖であり、内部的にはjava.lang.Enum



から派生したクラスの同じインスタンスによって表されjava.lang.Enum



。 そして最後に、 private



として宣言された素晴らしいordinal



フィールドがあります。これは、 ordinal()



メソッドによって返され、 ORMがデータベースに格納するために使用する値を格納します。



データベースのディレクトリから列挙要素の現在の識別子を読み取り、このフィールドに配置するだけです。 次に、 EnumType.ORDINAL



をデータベースに格納するための通常の方法で迅速かつ便利なアクセスを使用して、JavaのEnumの魅力をすべて保持し、識別子の同期とその関連性に問題がないようにします。



このアプローチはオブジェクトのシリアル化で問題を引き起こすように思えるかもしれませんが、そうではありません。Javaプラットフォームの仕様から次のことが一目瞭然です



1.12。 列挙定数のシリアル化

列挙定数は、通常のシリアル化可能または外部化可能なオブジェクトとは異なる方法でシリアル化されます。 列挙定数のシリアル化された形式は、その名前のみで構成されます。 定数のフィールド値はフォームに存在しません。




つまり、シリアル化中、列挙は常に文字列形式に変換され、数値は無視されます。 出来上がり!



今少し練習。 始めるために、この例のデータモデルを定義しましょう。



 CREATE SEQUENCE status_id; CREATE SEQUENCE product_id; CREATE TABLE status ( id INTEGER NOT NULL DEFAULT NEXT VALUE FOR status_id, code CHARACTER VARYING (32) NOT NULL, CONSTRAINT status_pk PRIMARY KEY (id), CONSTRAINT status_unq1 UNIQUE KEY (code) ); INSERT INTO status (code) VALUES ('NEW'); INSERT INTO status (code) VALUES ('ACTIVE'); INSERT INTO status (code) VALUES ('DELETED'); CREATE TABLE product ( id INTEGER NOT NULL DEFAULT NEXT VALUE FOR product_id, status_id INTEGER NOT NULL, title CHARACTER VARYING (128) NOT NULL, CONSTRAINT product_pk PRIMARY KEY (id), CONSTRAINT product_unq1 UNIQUE KEY (title), CONSTRAINT product_fk1 FOREIGN KEY (status_id) REFERENCES status (id) ON UPDATE CASCADE ON DELETE RESTRICT ); CREATE INDEX product_fki1 ON product (status_id);
      
      







ここで、Javaで同じデータスキームを説明します。 この場合、辞書の列挙とエンティティクラスの両方が定義されていることに注意してください。 均一なコードの繰り返しを避けるため、列挙のディレクトリはSystemDictionary



から継承されます。 また、 @MappedEnum



アノテーションに注意して@MappedEnum



。これは、データベースにどの列挙が反映されるかを決定するために将来使用します。



 public enum Status { NEW, ACTIVE, DELETED } @Retention(value = RetentionPolicy.RUNTIME) public @interface MappedEnum { Class<? extends Enum> enumClass(); } @MappedSuperclass public class SystemDictionary { @Id @GeneratedValue(generator = "entityIdGenerator") @Column(name = "id", nullable = false, unique = true) private Integer id; @Column(name = "code", nullable = false, unique = true, length = 32) private String code; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } } @Entity @Table(name = "status") @SequenceGenerator(name = "entityIdGenerator", sequenceName = "status_id") @MappedEnum(enumClass = Status.class) public class StatusEx extends SystemDictionary { } @Entity @Table(name = "product") @SequenceGenerator(name = "entityIdGenerator", sequenceName = "product_id") public class Product { @Id @GeneratedValue(generator = "entityIdGenerator") @Column(name = "id", nullable = false, unique = true) private Integer id; @Column(name = "status_id", nullable = false, unique = false) @Enumerated(EnumType.ORDINAL) private Status status; @Column(name = "title", nullable = false, unique = true) private String title; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public Status getStatus() { return status; } public void setStatus(Status status) { this.status = status; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } }
      
      







データベースから値を読み取ってordinal



フィールドに書き込む必要があります。また、 getEnumConstants()



からインデックスによる列挙のインスタンスを取得できるように、 values



配列を更新するvalues



も忘れないでください。乗り換えだけでなく、場所だけで非常に便利です。 次のコードのようなものを使用して、データベースへの接続を初期化した直後にこれを行うことができます。



 public interface SessionAction { void run(Session session); } public class EnumLoader implements SessionAction { @Override public void run(Session session) { Iterator<PersistentClass> mappingList = configuration.getClassMappings(); while (mappingList.hasNext()) { PersistentClass mapping = mappingList.next(); Class<?> clazz = mapping.getMappedClass(); if (!SystemDictionary.class.isAssignableFrom(clazz)) continue; if (!clazz.isAnnotationPresent(MappedEnum.class)) continue; MappedEnum mappedEnum = clazz.getAnnotation(MappedEnum.class); updateEnumIdentifiers(session, mappedEnum.enumClass(), (Class<SystemDictionary>) clazz); } } private void updateEnumIdentifiers( Session session, Class<? extends Enum> enumClass, Class<? extends SystemDictionary> entityClass) { List<SystemDictionary> valueList = (List<SystemDictionary>) session.createCriteria(entityClass).list(); int maxId = 0; Enum[] constants = enumClass.getEnumConstants(); Iterator<SystemDictionary> valueIterator = valueList.iterator(); while (valueIterator.hasNext()) { SystemDictionary value = valueIterator.next(); int valueId = value.getId().intValue(); setEnumOrdinal(Enum.valueOf(enumClass, value.getCode()), valueId); if (valueId > maxId) maxId = valueId; } Object valuesArray = Array.newInstance(enumClass, maxId + 1); for (Enum value : constants) Array.set(valuesArray, value.ordinal(), value); Field field; try { field = enumClass.getDeclaredField("$VALUES"); field.setAccessible(true); Field modifiersField = Field.class.getDeclaredField("modifiers"); modifiersField.setAccessible(true); modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); field.set(null, valuesArray); } catch (Exception ex) { throw new Exception("Can't update values array: ", ex); } } private void setEnumOrdinal(Enum object, int ordinal) { Field field; try { field = object.getClass().getSuperclass().getDeclaredField("ordinal"); field.setAccessible(true); field.set(object, ordinal); } catch (Exception ex) { throw new Exception("Can't update enum ordinal: " + ex); } } }
      
      







ご覧のように、データベースに反映されたクラスの完全なリストをHibernateから取得し、上記で宣言されたSystemDictionary



から継承し、同時に@MappedEnum



アノテーションを含むすべてのクラスを選択し、列挙クラスインスタンスの数値を更新します。 実際、それだけです。 今、私たちは冷静にできる:



  1. Java Enumとして列挙を@Enumerated(EnumType.ORDINAL)



    し、それらを含むフィールドを@Enumerated(EnumType.ORDINAL)



    として宣言します
  2. コードおよびデータベース内のディレクトリの同期を自動的に制御します
  3. コードで識別子を宣言し、データベースで識別子を照合する手順について心配する必要はありません。
  4. 文字列名による列挙値へのアクセスを含む便利なデータベースクエリを実行する
  5. ...
  6. 利益!


完全なZenを実現するために、データベースに余分な値が含まれていないこと、つまり、参照テーブルとコード内の列挙宣言が同期されていることを確認することを追加できます。



このアプローチは、JMSに基づく分散サービス指向アーキテクチャを備えたかなり大規模なシステム(50万行以上のソースコードなど)で私たち( オープンソーステクノロジー )によって使用されており、使いやすさと信頼性の両面で非常に優れていることが証明されています。 私があなたに望むこと:)



All Articles