Hibernateのカスタム型

1.概要

Hibernateは、Javaのオブジェクト指向モデルをデータベースのリレーショナルモデルにマッピングすることで、SQLとJDBCの間のデータ処理を単純化します。

基本的なJavaクラスのマッピングはHibernateに組み込まれていますが、カスタムタイプのマッピングはしばしば複雑です。

このチュートリアルでは、Hibernateによって基本的な型マッピングをカスタムJavaクラスに拡張する方法を説明します。それに加えて、カスタムタイプの一般的な例をいくつか見て、それらをHibernateのタイプマッピングメカニズムを使って実装します。

2.休止状態マッピングの種類

Hibernateは、データを格納するためにJavaオブジェクトをSQLクエリに変換するためにマッピング型を使用します。同様に、データの取得中にSQL ResultSetをJavaオブジェクトに変換するためにマッピング型を使用します。

一般に、Hibernateは型をエンティティ型と値型に分類します。 具体的には、エンティティ型はドメイン固有のJavaエンティティをマッピングするために使用されるため、アプリケーション内の他の型とは無関係に存在します。対照的に、値型は代わりにデータオブジェクトをマッピングするために使用され、ほとんどの場合エンティティによって所有されます。

このチュートリアルでは、さらに次のように分類されるValue型のマッピングに焦点を当てます。

  • 基本型 - 基本Java型へのマッピング

  • 埋め込み - 複合Java型/POJOのマッピング

  • コレクション - 基本および複合Javaのコレクションのマッピング

タイプ

3. Mavenの依存関係

カスタムHibernate型を作成するには、https://search.maven.org/search?q=g:org.hibernate%20a:hibernate-core[hibernate-core]依存関係が必要です。

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>5.3.6.Final</version>
</dependency>

4. Hibernateのカスタムタイプ

ほとんどのユーザードメインでHibernateの基本マッピングタイプを使用できます。ただし、カスタム型を実装する必要があるユースケースは数多くあります。

Hibernateはカスタム型の実装を比較的簡単にします。 Hibernateでカスタムタイプを実装する方法は3つあります。それぞれについて詳しく説明しましょう。

4.1. BasicType を実装する

Hibernateの __BasicType、またはその特定の実装の1つである AbstractSingleColumnStandardBasicTypeを実装することで、カスタム基本型を作成できます。

最初のカスタム型を実装する前に、基本型を実装するための一般的なユースケースを見てみましょう。日付をVARCHARとして格納する従来のデータベースを使用する必要があるとします。通常、 Hibernateはこれを String Java型にマップします。そのため、アプリケーション開発者にとって日付の検証が難しくなります。

__LocalDate Java型をVARCHARとして格納する LocalDateString __typeを実装しましょう。

public class LocalDateStringType
  extends AbstractSingleColumnStandardBasicType<LocalDate> {

    public static final LocalDateStringType INSTANCE = new LocalDateStringType();

    public LocalDateStringType() {
        super(VarcharTypeDescriptor.INSTANCE, LocalDateStringJavaDescriptor.INSTANCE);
    }

    @Override
    public String getName() {
        return "LocalDateString";
    }
}

このコードで最も重要なことはコンストラクタのパラメータです。

まず、これはHibernateのSQL型表現である SqlTypeDescriptor のインスタンスです。この例ではVARCHARです。そして2番目の引数は、Javaの型を表す JavaTypeDescriptor のインスタンスです。

これで、 LocalDate をVARCHARとして格納および取得するための __LocalDateStringJavaDescriptor __を実装できます。

public class LocalDateStringJavaDescriptor extends AbstractTypeDescriptor<LocalDate> {

    public static final LocalDateStringJavaDescriptor INSTANCE =
      new LocalDateStringJavaDescriptor();

    public LocalDateStringJavaDescriptor() {
        super(LocalDate.class, ImmutableMutabilityPlan.INSTANCE);
    }

   //other methods
}

次に、Java型をSQLに変換するために _wrap unwrap methodsをオーバーライドする必要があります。 unwrapから始めましょう:_

@Override
public <X> X unwrap(LocalDate value, Class<X> type, WrapperOptions options) {

    if (value == null)
        return null;

    if (String.class.isAssignableFrom(type))
        return (X) LocalDateType.FORMATTER.format(value);

    throw unknownUnwrap(type);
}

次に、 _ wrap _ method:

@Override
public <X> LocalDate wrap(X value, WrapperOptions options) {
    if (value == null)
        return null;

    if(String.class.isInstance(value))
        return LocalDate.from(LocalDateType.FORMATTER.parse((CharSequence) value));

    throw unknownWrap(value.getClass());
}

__unwrap() PreparedStatement bindingの間に呼び出され、 LocalDate __をString型に変換します。これはVARCHARにマップされます。

同様に、 __ wrap()は、 String をJavaの LocalDate に変換するために ResultSet __retrievalの間に呼び出されます。

最後に、Entityクラスでカスタム型を使用できます。

@Entity
@Table(name = "OfficeEmployee")
public class OfficeEmployee {

    @Column
    @Type(type = "com.baeldung.hibernate.customtypes.LocalDateStringType")
    private LocalDate dateOfJoining;

   //other fields and methods
}

後で、このタイプをHibernateに登録する方法がわかります。そしてその結果、完全修飾クラス名の代わりに登録キーを使用してこのタイプを参照してください。

4.2. UserType を実装する

Hibernateのさまざまな基本型で、カスタム基本型を実装する必要があることは非常にまれです。対照的に、より一般的なユースケースは複雑なJavaドメインオブジェクトをデータベースにマップすることです。このようなドメインオブジェクトは通常、複数のデータベース列に格納されています。

それでは、 UserTypeを実装して、複雑な PhoneNumber__オブジェクトを実装しましょう。

public class PhoneNumberType implements UserType {
    @Override
    public int[]sqlTypes() {
        return new int[]{Types.INTEGER, Types.INTEGER, Types.INTEGER};
    }

    @Override
    public Class returnedClass() {
        return PhoneNumber.class;
    }

   //other methods
}

ここで、オーバーライドされた __sqlTypes methodは、私たちの PhoneNumber クラスで宣言されているのと同じ順序で、フィールドのSQL型を返します。同様に、 returnedClass methodは、 PhoneNumber __Java型を返します。

あとは、 BasicType の場合と同じように、Java型とSQL型の間で変換するメソッドを実装することだけです。

まず、 __nullSafeGet __method:

@Override
public Object nullSafeGet(ResultSet rs, String[]names,
  SharedSessionContractImplementor session, Object owner)
  throws HibernateException, SQLException {
    int countryCode = rs.getInt(names[0]);

    if (rs.wasNull())
        return null;

    int cityCode = rs.getInt(names[1]);
    int number = rs.getInt(names[2]);
    PhoneNumber employeeNumber = new PhoneNumber(countryCode, cityCode, number);

    return employeeNumber;
}

次に、 __ nullSafeSet __method:

@Override
public void nullSafeSet(PreparedStatement st, Object value,
  int index, SharedSessionContractImplementor session)
  throws HibernateException, SQLException {

    if (Objects.isNull(value)) {
        st.setNull(index, Types.INTEGER);
    } else {
        PhoneNumber employeeNumber = (PhoneNumber) value;
        st.setInt(index,employeeNumber.getCountryCode());
        st.setInt(index+1,employeeNumber.getCityCode());
        st.setInt(index+2,employeeNumber.getNumber());
    }
}

最後に、 _OfficeEmployee _entityクラスにカスタムの____PhoneNumberTypeを宣言できます。

@Entity
@Table(name = "OfficeEmployee")
public class OfficeEmployee {

    @Columns(columns = { @Column(name = "country__code"),
      @Column(name = "city__code"), @Column(name = "number") })
    @Type(type = "com.baeldung.hibernate.customtypes.PhoneNumberType")
    private PhoneNumber employeeNumber;

   //other fields and methods
}

4.3. CompositeUserType の実装

__UserTypeの実装 は、直接的な型に適しています。ただし、複雑なJava型(コレクション型およびカスケード型の複合型)のマッピングには、さらに高度な作業が必要です。 Hibernateは CompositeUserType __interfaceを実装することによってそのような型をマッピングすることを可能にします。

それでは、先ほど使った __OfficeEmployee エンティティのために AddressType __を実装することによってこれが実際に動作しているのを見てみましょう。

public class AddressType implements CompositeUserType {

    @Override
    public String[]getPropertyNames() {
        return new String[]{ "addressLine1", "addressLine2",
          "city", "country", "zipcode" };
    }

    @Override
    public Type[]getPropertyTypes() {
        return new Type[]{ StringType.INSTANCE,
          StringType.INSTANCE,
          StringType.INSTANCE,
          StringType.INSTANCE,
          IntegerType.INSTANCE };
    }

   //other methods
}

型プロパティのインデックスをマッピングする UserTypes とは対照的に、 __Address classの CompositeType __mapsプロパティ名。さらに重要なことに、getPropertyTypeメソッドは各プロパティのマッピングタイプを返します。

さらに、 _PreparedStatement ResultSet indexesをtypeプロパティにマッピングするための getPropertyValue setPropertyValue methodsも実装する必要があります。例として、 AddressTypeに _getPropertyValue __を考えます。

@Override
public Object getPropertyValue(Object component, int property) throws HibernateException {

    Address empAdd = (Address) component;

    switch (property) {
    case 0:
        return empAdd.getAddressLine1();
    case 1:
        return empAdd.getAddressLine2();
    case 2:
        return empAdd.getCity();
    case 3:
        return empAdd.getCountry();
    case 4:
        return Integer.valueOf(empAdd.getZipCode());
    }

    throw new IllegalArgumentException(property + " is an invalid property index for class type "
      + component.getClass().getName());
}

最後に、Java型とSQL型の間の変換のために __nullSafeGet nullSafeSet methodsを実装する必要があります。これは、 PhoneNumberTypeで前に行ったことと似ています。

  • CompositeTypeは、一般的に Embeddable __typesの代替マッピングメカニズムとして実装されています。

4.4. 型パラメータ化

カスタム型を作成するだけでなく、** Hibernateではパラメータに基づいて型の動作を変更することもできます。

たとえば、 __OfficeEmployeeに Salary を格納する必要があるとします。 __さらに重要なことに、アプリケーションは給与額を現地の現地通貨金額に変換する必要があります。

それでは、パラメータとして __currency をパラメータとして受け取る SalaryType __SalaryTypeを実装しましょう。

public class SalaryType implements CompositeUserType, DynamicParameterizedType {

    private String localCurrency;

    @Override
    public void setParameterValues(Properties parameters) {
        this.localCurrency = parameters.getProperty("currency");
    }

   //other method implementations from CompositeUserType
}

この例から CompositeUserType メソッドをスキップして、パラメータ化に焦点を当てていることに注意してください。ここでは、Hibernateの DynamicParameterizedType を実装し、 __setParameterValues()メソッドをオーバーライドしています。これで、 SalaryTypeは currency __パラメータを受け入れ、それを格納する前に任意の量を変換します。

Salaryを宣言しながら、 currency__をパラメータとして渡します。

@Entity
@Table(name = "OfficeEmployee")
public class OfficeEmployee {

    @Type(type = "com.baeldung.hibernate.customtypes.SalaryType",
      parameters = { @Parameter(name = "currency", value = "USD") })
    @Columns(columns = { @Column(name = "amount"), @Column(name = "currency") })
    private Salary salary;

   //other fields and methods
}

5.基本型レジストリ

Hibernateは BasicTypeRegistry ですべての組み込み基本型のマッピングを管理します。したがって、そのような型のマッピング情報に注釈を付ける必要がなくなります。

さらに、Hibernateでは、基本型と同じように BasicTypeRegistry にカスタム型を登録することができます。通常、アプリケーションは __SessionFactoryのブートストラップ中にカスタム型を登録します。 これまでに実装した LocalDateString __typeを登録することで、これを理解できます。

private static SessionFactory makeSessionFactory() {
    ServiceRegistry serviceRegistry = StandardServiceRegistryBuilder()
      .applySettings(getProperties()).build();

    MetadataSources metadataSources = new MetadataSources(serviceRegistry);
    Metadata metadata = metadataSources.getMetadataBuilder()
      .applyBasicType(LocalDateStringType.INSTANCE)
      .build();

    return metadata.getSessionFactoryBuilder().build()
}

private static Properties getProperties() {
   //return hibernate properties
}

したがって、** 型マッピングで完全修飾クラス名を使用するという制限がなくなります。

@Entity
@Table(name = "OfficeEmployee")
public class OfficeEmployee {

    @Column
    @Type(type = "LocalDateString")
    private LocalDate dateOfJoining;

   //other methods
}

ここで、 LocalDateString は、 __ LocalDateString Typeがマップされるキーです。

あるいは、__TypeDefsを定義することで型登録をスキップすることができます。

@TypeDef(name = "PhoneNumber", typeClass = PhoneNumberType.class,
  defaultForType = PhoneNumber.class)
@Entity
@Table(name = "OfficeEmployee")
public class OfficeEmployee {

    @Columns(columns = {@Column(name = "country__code"),
    @Column(name = "city__code"),
    @Column(name = "number")})
    private PhoneNumber employeeNumber;

   //other methods
}

6.まとめ

このチュートリアルでは、Hibernateでカスタムタイプを定義するための複数のアプローチについて説明しました。さらに、** 新しいカスタムタイプが便利になることができるいくつかの一般的なユースケースに基づいて、私たちはエンティティクラスのためにいくつかのカスタムタイプを実装しました。

いつものように、コードサンプルは利用可能ですhttps://github.com/eugenp/tutorials/tree/master/persistence-modules/hibernate5[GitHubで動く]。