web-dev-qa-db-ja.com

JPAの汎用列挙型コンバーターを作成することは可能ですか?

列挙型を大文字で格納するConverter for JPAを書きたかった。私たちが遭遇する一部の列挙型は、大文字のみを使用するという慣例に従っていないため、それらがリファクタリングされるまで、将来の値を保存します。

私がこれまでに得たもの:

package student;

public enum StudentState {

    Started,
    Mentoring,
    Repeating,
    STUPID,
    GENIUS;
}

「開始済み」を「開始済み」などとして保存したい。

package student;

import jpa.EnumUppercaseConverter;

import javax.persistence.*;
import Java.io.Serializable;
import Java.util.Date;

@Entity
@Table(name = "STUDENTS")
public class Student implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    @Column(name = "ID")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long mId;

    @Column(name = "LAST_NAME", length = 35)
    private String mLastName;

    @Column(name = "FIRST_NAME", nullable = false, length = 35)
    private String mFirstName;

    @Column(name = "BIRTH_DATE", nullable = false)
    @Temporal(TemporalType.DATE)
    private Date mBirthDate;

    @Column(name = "STUDENT_STATE")
    @Enumerated(EnumType.STRING)
    @Convert(converter = EnumUppercaseConverter.class)
    private StudentState studentState;

}

現在、コンバーターは次のようになっています。

package jpa;


import javax.persistence.AttributeConverter;
import Java.util.EnumSet;

public class EnumUppercaseConverter<E extends Enum<E>> implements AttributeConverter<E, String> {

    private Class<E> enumClass;

    @Override
    public String convertToDatabaseColumn(E e) {
        return e.name().toUpperCase();
    }

    @Override
    public E convertToEntityAttribute(String s) {
        // which enum is it?
        for (E en : EnumSet.allOf(enumClass)) {
            if (en.name().equalsIgnoreCase(s)) {
                return en;
            }
        }
        return null;
    }

}

機能しないのは、実行時にenumClassがどうなるかわからないということです。そして、この情報を@Converterアノテーションでコンバーターに渡す方法を理解できませんでした。

それで、コンバーターにパラメーターを追加したり、少しチートする方法はありますか?それとも別の方法がありますか?

私はEclipseLink 2.4.2を使用しています

ありがとう!

24
wemu

必要なことは、ジェネリック基本クラスを記述し、永続化する列挙型ごとにそれを拡張することです。次に、@Converterアノテーションで拡張型を使用します。

public abstract class GenericEnumUppercaseConverter<E extends Enum<E>> implements AttributeConverter<E, String> {
    ...
}

public FooConverter
    extends GenericEnumUppercaseConverter<Foo> 
    implements AttributeConverter<Foo, String> // See Bug HHH-8854
{
    public FooConverter() {
        super(Foo.class);
    }
}

ここで、Fooは処理する列挙型です。

代わりに、カスタムアノテーションを定義し、JPAプロバイダーにパッチを適用してこのアノテーションを認識させることもできます。このようにして、マッピング情報を作成するときにフィールドタイプを調べ、必要な列挙型を純粋に汎用的なコンバーターに渡すことができます。

関連:

18
Aaron Digulla

私がこれを作成した@scottbソリューションに基づいて、Hibernate 4.3に対してテストしました:(Hibernateクラスはなく、JPAで正常に実行する必要があります)

インターフェース列挙型は以下を実装する必要があります:

public interface PersistableEnum<T> {
    public T getValue();
}

基本抽象コンバーター:

@Converter
public abstract class AbstractEnumConverter<T extends Enum<T> & PersistableEnum<E>, E> implements AttributeConverter<T, E> {
    private final Class<T> clazz;

    public AbstractEnumConverter(Class<T> clazz) {
        this.clazz = clazz;
    }

    @Override
    public E convertToDatabaseColumn(T attribute) {
        return attribute != null ? attribute.getValue() : null;
    }

    @Override
    public T convertToEntityAttribute(E dbData) {
        T[] enums = clazz.getEnumConstants();

        for (T e : enums) {
            if (e.getValue().equals(dbData)) {
                return e;
            }
        }

        throw new UnsupportedOperationException();
    }
}

列挙型ごとにコンバータークラスを作成する必要があります。列挙型内に静的クラスを作成する方が簡単だと思います(jpa/hibernateは列挙型のインターフェースを提供するだけで十分です)。

public enum IndOrientation implements PersistableEnum<String> {
    LANDSCAPE("L"), PORTRAIT("P");

    private final String value;

    @Override
    public String getValue() {
        return value;
    }

    private IndOrientation(String value) {
        this.value= value;
    }

    public static class Converter extends AbstractEnumConverter<IndOrientation, String> {
        public Converter() {
            super(IndOrientation.class);
        }
    }
}

そして、アノテーション付きのマッピング例:

...
@Convert(converter = IndOrientation.Converter.class)
private IndOrientation indOrientation;
...

いくつかの変更により、IntegerEnumインターフェースを作成し、それを一般化することができます。

16
ChRoNoN

この問題に対する私の解決策は似ており、JPA 2.1コンバーター機能も利用しています。残念ながら、Java 8のジェネリック型は具体化されていないため、Java enumごとに個別のクラスを作成するのを避ける簡単な方法はないようです。データベース形式との間で変換できるようにしたいこと。

ただし、enumコンバータクラスを純粋なボイラープレートに書き込むプロセスを減らすことができます。このソリューションのコンポーネントは次のとおりです。

  1. Encodeableインターフェース;各列挙型定数のStringトークンへのアクセスを許可する列挙型クラスのコントラクト(一致するトークンの列挙型定数を取得するためのファクトリ)
  2. AbstractEnumConverterクラス;列挙型定数との間でトークンを変換するための共通コードを提供します
  3. Encodeableインターフェースを実装するJava列挙型クラス
  4. AbstractEnumConverterクラスを拡張するJPAコンバータークラス

Encodeableインターフェースはシンプルで、列挙型定数を取得するための静的なファクトリメソッドforToken()が含まれています。

public interface Encodeable {

    String token();

    public static <E extends Enum<E> & Encodeable> E forToken(Class<E> cls, String tok) {
        final String t = tok.trim().toUpperCase();
        return Stream.of(cls.getEnumConstants())
                .filter(e -> e.token().equals(t))
                .findFirst()
                .orElseThrow(() -> new IllegalArgumentException("Unknown token '" +
                        tok + "' for enum " + cls.getName()));
    }
}

AbstractEnumConverterクラスは、やはりシンプルなジェネリッククラスです。これはJPA 2.1 AttributeConverterインターフェースを実装しますが、そのメソッドの実装を提供しません(このクラスは適切な列挙定数を取得するために必要な具象型を認識できないため)。代わりに、具象コンバータークラスがチェーンするヘルパーメソッドを定義します。

public abstract class AbstractEnumConverter<E extends Enum<E> & Encodeable>
            implements AttributeConverter<E, String> {

    public String toDatabaseColumn(E attr) {
        return (attr == null)
                ? null
                : attr.token();
    }

    public E toEntityAttribute(Class<E> cls, String dbCol) {
        return (dbCol == null)
                ? null
                : Encodeable.forToken(cls, dbCol);
    }
}

JPA 2.1コンバーター機能を使用してデータベースに永続化できる具体的な列挙型クラスの例を以下に示します(Encodeableを実装し、各列挙型定数のトークンがプライベートフィールドとして定義されていることに注意してください) ):

public enum GenderCode implements Encodeable {

    MALE   ("M"), 
    FEMALE ("F"), 
    OTHER  ("O");

    final String e_token;

    GenderCode(String v) {
        this.e_token = v;
    }

    @Override
    public String token() {
        return this.e_token;
    }
}

すべてのJPA 2.1 Converterクラスのボイラープレートは次のようになります(そのようなすべてのコンバータはAbstractEnumConverterを拡張し、JPA AttributeConverterインターフェースのメソッドの実装を提供する必要があることに注意してください):

@Converter
public class GenderCodeConverter 
            extends AbstractEnumConverter<GenderCode> {

    @Override
    public String convertToDatabaseColumn(GenderCode attribute) {
        return this.toDatabaseColumn(attribute);
    }

    @Override
    public GenderCode convertToEntityAttribute(String dbData) {
        return this.toEntityAttribute(GenderCode.class, dbData);
    }
}
5
scottb