マッピング

MappingJdbcConverter により、豊富なマッピングサポートが提供されます。MappingJdbcConverter には、ドメインオブジェクトをデータ行にマッピングできる豊富なメタデータモデルがあります。マッピングメタデータモデルは、ドメインオブジェクトのアノテーションを使用して作成されます。ただし、インフラストラクチャは、メタデータ情報の唯一のソースとしてアノテーションを使用することに限定されません。MappingJdbcConverter では、一連の規則に従って、追加のメタデータを提供せずにオブジェクトを行にマップすることもできます。

このセクションでは、オブジェクトを行にマッピングするための規則の使用方法や、アノテーションベースのマッピングメタデータでこれらの規則をオーバーライドする方法など、MappingJdbcConverter の機能について説明します。

この章を続ける前に、オブジェクトマッピングの基礎の基本について参照してください。

規約ベースのマッピング

MappingJdbcConverter には、追加のマッピングメタデータが提供されていない場合にオブジェクトを行にマッピングするためのいくつかの規則があります。規則は次のとおりです。

  • 短い Java クラス名は、次の方法でテーブル名にマップされます。com.bigbank.SavingsAccount クラスは、SAVINGS_ACCOUNT テーブル名にマップされます。同じ名前のマッピングが、フィールドを列名にマッピングするために適用されます。例: firstName フィールドは FIRST_NAME 列にマップされます。カスタム NamingStrategy を提供することにより、このマッピングを制御できます。詳細については、マッピング設定を参照してください。プロパティ名またはクラス名から派生したテーブル名と列名は、デフォルトで引用符なしで SQL ステートメントで使用されます。RelationalMappingContext.setForceQuote(true) を設定することにより、この動作を制御できます。

  • コンバーターは、CustomConversions に登録されている Spring コンバーターを使用して、オブジェクトプロパティの行列および値へのデフォルトのマッピングをオーバーライドします。

  • オブジェクトのフィールドは、行の列との間の変換に使用されます。パブリック JavaBean プロパティは使用されません。

  • コンストラクター引数名が行のトップレベルの列名と一致する非ゼロ引数のコンストラクターが 1 つある場合は、そのコンストラクターが使用されます。それ以外の場合は、引数なしのコンストラクターが使用されます。引数がゼロではないコンストラクターが複数ある場合、例外がスローされます。詳細についてはオブジェクトの作成を参照してください。

エンティティでサポートされている型

現在、次の型のプロパティがサポートされています。

  • すべてのプリミティブ型とそれらのボックス化された型 (intfloatIntegerFloat など)

  • 列挙型は名前にマップされます。

  • String

  • java.util.Datejava.time.LocalDatejava.time.LocalDateTimejava.time.LocalTime

  • データベースでサポートされている場合、上記の型の配列とコレクションは、配列型の列にマップできます。

  • データベースドライバーが受け入れるもの。

  • 他のエンティティへの参照。これらは 1 対 1 の関連、または埋め込み型とみなされます。1 対 1 の関連エンティティが id 属性を持つことはオプションです。参照先エンティティのテーブルには、参照元エンティティに基づく名前を持つ追加の列があることが期待されます ( バックリファレンスを参照)。埋め込みエンティティには id は必要ありません。存在する場合、特別な意味を持たずに通常の属性としてマップされます。

  • Set<some entity> は、1 対多の関連と見なされます。参照エンティティのテーブルには、参照エンティティに基づく名前を持つ追加の列が必要です。バックリファレンスを参照してください。

  • Map<simple type, some entity> は、限定された 1 対多の関連とみなされます。参照されるエンティティのテーブルには 2 つの追加列があることが期待されます。1 つは外部キーの参照エンティティに基づいて名前が付けられ ( バックリファレンスを参照)、もう 1 つは同じ名前でマップキーに追加の _key サフィックスが付いています。

  • List<some entity> は Map<Integer, some entity> としてマップされます。同じ追加の列が期待されており、使用される名前も同じ方法でカスタマイズできます。

    ListSetMap の場合、後方参照の命名は、それぞれ NamingStrategy.getReverseColumnName(RelationalPersistentEntity<?> owner) および NamingStrategy.getKeyColumn(RelationalPersistentProperty property) を実装することで制御できます。あるいは、属性に @MappedCollection(idColumn="your_column_name", keyColumn="your_key_column_name") のアノテーションを付けることもできます。Set のキー列を指定しても効果はありません。

  • 適切なカスタムコンバーターを登録した型。

マッピングアノテーションの概要

RelationalConverter は、メタデータを使用してオブジェクトの行へのマッピングを駆動できます。次のアノテーションを使用できます。

  • @Id: 主キーをマークするためにフィールドレベルで適用されます。

  • @Table: このクラスがデータベースへのマッピングの候補であることを示すために、クラスレベルで適用されます。データベースが保存されているテーブルの名前を指定できます。

  • @Transient: デフォルトでは、すべてのフィールドが行にマップされます。このアノテーションは、それが適用されるフィールドをデータベースに格納することから除外します。コンバーターはコンストラクターの引数の値を具体化できないため、永続的なコンストラクター内では一時的なプロパティを使用できません。

  • @PersistenceCreator: データベースからオブジェクトをインスタンス化するときに使用する、特定のコンストラクターまたは静的ファクトリメソッド (パッケージで protected メソッドであっても) をマークします。コンストラクターの引数は、名前によって取得された行の値にマップされます。

  • @Value: このアノテーションは Spring Framework の一部です。マッピングフレームワーク内で、コンストラクター引数に適用できます。これにより、Spring 式言語ステートメントを使用して、ドメインオブジェクトの構築に使用される前に、データベースで取得されたキーの値を変換できます。特定の行の列を参照するには、次のような式を使用する必要があります。: @Value("#root.myProperty") ここで、root は指定された Row のルートを指します。

  • @Column: フィールドレベルで適用され、行に表示される列の名前を記述し、クラスのフィールド名とは異なる名前にします。@Column アノテーションで指定された名前は、SQL ステートメントで使用される場合は常に引用符で囲まれます。ほとんどのデータベースでは、これはこれらの名前で大文字と小文字が区別されることを意味します。また、これらの名前に特殊文字を使用できることも意味します。ただし、他のツールで問題が発生する可能性があるため、これはお勧めしません。

  • @Version: フィールドレベルで適用されると、オプティミスティックロックに使用され、保存操作時に変更がチェックされます。値は null (プリミティブ型の場合は zero ) であり、新しいエンティティのマーカーとして考慮されます。最初に格納される値は zero (プリミティブ型の場合は one ) です。バージョンは更新のたびに自動的に増加します。

詳細については、楽観的ロックを参照してください。

マッピングメタデータインフラストラクチャは、テクノロジーに依存しない別の spring-data-commons プロジェクトで定義されます。JDBC サポートでは、アノテーションベースのメタデータをサポートするために特定のサブクラスが使用されます。(需要があれば) 他の戦略も導入できます。

参照エンティティ

参照エンティティの処理は制限されています。これは、上記の集約ルートの考え方に基づいています。別のエンティティを参照する場合、そのエンティティは定義上、集約の一部です。そのため、参照を削除すると、以前に参照されたエンティティが削除されます。これは、参照が 1-1 または 1-n であるが、n-1 または n-m ではないことも意味します。

n-1 または n-m の参照がある場合、定義上、2 つの別個の集約を扱っています。それらの間の参照は、Spring Data JDBC で適切にマップされる単純な id 値としてエンコードされる場合があります。これらをエンコードするより良い方法は、AggregateReference のインスタンスにすることです。AggregateReference は、id 値を別の集約への参照としてマークするラッパーです。また、その集約の型は、型パラメーターでエンコードされます。

バックリファレンス

集約内のすべての参照は、データベース内で逆方向の外部キー関連になります。デフォルトでは、外部キー列の名前は参照エンティティのテーブル名です。

あるいは、@Table アノテーションを無視して、参照エンティティのエンティティ名で名前を付けることも選択できます。この動作を有効にするには、RelationalMappingContext で setForeignKeyNaming(ForeignKeyNaming.IGNORE_RENAMING) を呼び出します。

List および Map 参照の場合、リストインデックスまたはマップキーを保持するために追加の列が必要です。これは、追加の _KEY サフィックスを持つ外部キー列に基づいています。

これらの逆参照の名前をまったく別の方法で付けたい場合は、必要に応じて NamingStrategy.getReverseColumnName(RelationalPersistentEntity<?> owner) を実装できます。

AggregateReference の宣言と設定
class Person {
	@Id long id;
	AggregateReference<Person, Long> bestFriend;
}

// ...

Person p1, p2 = // some initialization

p1.bestFriend = AggregateReference.to(p2.id);

後方参照やマップやリストのキー列の実際の値を保持する属性をエンティティに含めないでください。これらの値をドメインモデルで使用できるようにしたい場合は、AfterConvertCallback でこれを実行し、値を一時値に保存することをお勧めします。

ネーミング戦略

慣例により、Spring Data は NamingStrategy を適用して、デフォルトでスネークケース [Wikipedia] (英語) になるテーブル名、列名、スキーマ名を決定します。firstName という名前のオブジェクトプロパティは first_name になります。アプリケーションコンテキストで NamingStrategy (Javadoc) を指定することで、これを微調整できます。

テーブル名をオーバーライドする

テーブルの命名方法がデータベースのテーブル名と一致しない場合は、テーブル名を Table (Javadoc) アノテーションでオーバーライドできます。このアノテーションの要素 value はカスタムテーブル名を提供します。次の例では、MyEntity クラスをデータベース内の CUSTOM_TABLE_NAME テーブルにマップします。

@Table("CUSTOM_TABLE_NAME")
class MyEntity {
    @Id
    Integer id;

    String name;
}

Spring Data の SpEL サポートを使用してテーブル名を動的に作成できます。テーブル名は生成されるとキャッシュされるため、マッピングコンテキストごとにのみ動的になります。

列名をオーバーライドする

列の命名方法がデータベーステーブル名と一致しない場合は、テーブル名を Column (Javadoc) アノテーションでオーバーライドできます。このアノテーションの要素 value はカスタム列名を提供します。次の例では、MyEntity クラスの name プロパティをデータベースの CUSTOM_COLUMN_NAME 列にマップします。

class MyEntity {
    @Id
    Integer id;

    @Column("CUSTOM_COLUMN_NAME")
    String name;
}

MappedCollection (Javadoc) アノテーションは、参照型(1 対 1 の関連)またはセット、リスト、マップ(1 対多の関連)で使用できます。アノテーションの idColumn 要素は、他のテーブルの id 列を参照する外部キー列のカスタム名を提供します。次の例では、MySubEntity クラスに対応するテーブルに NAME 列と、MyEntity ID の CUSTOM_MY_ENTITY_ID_COLUMN_NAME 列が関連の理由で含まれています。

class MyEntity {
    @Id
    Integer id;

    @MappedCollection(idColumn = "CUSTOM_MY_ENTITY_ID_COLUMN_NAME")
    Set<MySubEntity> subEntities;
}

class MySubEntity {
    String name;
}

List および Map を使用する場合、List のデータセットの位置または Map のエンティティのキー値の追加列が必要です。この追加の列名は、MappedCollection (Javadoc) アノテーションの keyColumn 要素を使用してカスタマイズできます。

class MyEntity {
    @Id
    Integer id;

    @MappedCollection(idColumn = "CUSTOM_COLUMN_NAME", keyColumn = "CUSTOM_KEY_COLUMN_NAME")
    List<MySubEntity> name;
}

class MySubEntity {
    String name;
}

Spring Data の SpEL サポートを使用して列名を動的に作成できます。生成された名前はキャッシュされるため、マッピングコンテキストごとにのみ動的になります。

埋め込みエンティティ

埋め込みエンティティは、データベースにテーブルが 1 つしかない場合でも、java データモデルに値オブジェクトを保持するために使用されます。次の例では、MyEntity が @Embedded アノテーションでマップされています。この結果、データベースには、id および name (EmbeddedEntity クラスの)の 2 つの列を持つテーブル my_entity が期待されます。

ただし、結果セット内で name 列が実際に null である場合、@Embedded の onEmpty に従って、プロパティ embeddedEntity 全体が null に設定されます。ネストされたすべてのプロパティが null である場合、null はオブジェクトになります。
この動作とは反対に、USE_EMPTY は、デフォルトコンストラクターまたは結果セットから NULL 可能パラメーター値を受け入れるコンストラクターを使用して、新しいインスタンスを作成しようとします。

例 1: 埋め込みオブジェクトのサンプルコード
class MyEntity {

    @Id
    Integer id;

    @Embedded(onEmpty = USE_NULL) (1)
    EmbeddedEntity embeddedEntity;
}

class EmbeddedEntity {
    String name;
}
1 null の name の場合、NullembeddedEntityUSE_EMPTY を使用して、name プロパティの潜在的な null 値で embeddedEntity をインスタンス化します。

エンティティで複数回値オブジェクトが必要な場合は、@Embedded アノテーションのオプションの prefix 要素を使用してこれを実現できます。この要素はプレフィックスを表し、埋め込みオブジェクトの各列名の先頭に追加されます。

@Embedded(onEmpty = USE_NULL) および @Embedded(onEmpty = USE_EMPTY) のショートカット @Embedded.Nullable および @Embedded.Empty を使用して、冗長性を減らし、同時に JSR-305 @javax.annotation.Nonnull を適切に設定します。

class MyEntity {

    @Id
    Integer id;

    @Embedded.Nullable (1)
    EmbeddedEntity embeddedEntity;
}
1@Embedded(onEmpty = USE_NULL) のショートカット。

Collection または Map を含む埋め込みエンティティは、少なくとも空のコレクションまたはマップを含むため、常に空ではないとみなされます。このようなエンティティは、@Embedded(onEmpty = USE_NULL) を使用する場合でも、null になることはありません。

読み取り専用プロパティ

@ReadOnlyProperty アノテーションが付けられた属性は、Spring Data によってデータベースに書き込まれませんが、エンティティがロードされるときに読み取られます。

Spring Data は、エンティティの書き込み後にエンティティを自動的にリロードしません。そのような列のデータベースで生成されたデータを表示する場合は、明示的にリロードする必要があります。

アノテーション付き属性がエンティティまたはエンティティのコレクションである場合、個別のテーブル内の 1 つ以上の個別の行で表されます。Spring Data は、これらの行の挿入、削除、更新を実行しません。

挿入専用プロパティ

@InsertOnlyProperty でアノテーションが付けられた属性は、挿入操作中に Spring Data によってのみデータベースに書き込まれます。更新の場合、これらのプロパティは無視されます。

@InsertOnlyProperty は、集約ルートに対してのみサポートされています。

カスタマイズされたオブジェクト構築

マッピングサブシステムでは、コンストラクターに @PersistenceConstructor アノテーションを付けることにより、オブジェクトの構成をカスタマイズできます。コンストラクターのパラメーターに使用される値は、次の方法で解決されます。

  • パラメーターに @Value アノテーションが付けられている場合、指定された式が評価され、結果がパラメーター値として使用されます。

  • Java 型に、入力行の指定されたフィールドと名前が一致するプロパティがある場合、そのプロパティ情報を使用して、入力フィールド値を渡す適切なコンストラクターパラメーターを選択します。これは、パラメーター名情報が Java .class ファイルに存在する場合にのみ機能します。これは、デバッグ情報を使用してソースをコンパイルするか、Java 8 の javac の -parameters コマンドラインスイッチを使用して実現できます。

  • そうでない場合、MappingException がスローされ、指定されたコンストラクターパラメーターをバインドできなかったことを示します。

class OrderItem {

  private @Id final String id;
  private final int quantity;
  private final double unitPrice;

  OrderItem(String id, int quantity, double unitPrice) {
    this.id = id;
    this.quantity = quantity;
    this.unitPrice = unitPrice;
  }

  // getters/setters omitted
}

明示的なコンバーターを使用したマッピングのオーバーライド

Spring Data を使用すると、カスタムコンバーターを登録して、データベース内での値のマッピングメソッドに影響を与えることができます。現在、コンバーターはプロパティレベルでのみ適用されます。つまり、ドメイン内の単一の値をデータベース内の単一の値に変換したり、その逆に変換したりすることしかできません。複雑なオブジェクトと複数の列の間の変換はサポートされていません。

登録済みの Spring コンバーターを使用したプロパティの記述

次の例は、Boolean オブジェクトから String 値に変換する Converter の実装を示しています。

import org.springframework.core.convert.converter.Converter;

@WritingConverter
public class BooleanToStringConverter implements Converter<Boolean, String> {

    @Override
    public String convert(Boolean source) {
        return source != null && source ? "T" : "F";
    }
}

ここで注意すべき点がいくつかあります。Boolean と String はどちらも単純な型であるため、Spring Data はこのコンバーターを適用する方向(読み取りまたは書き込み)のヒントを必要とします。このコンバーターに @WritingConverter のアノテーションを付けることにより、すべての Boolean プロパティを String としてデータベースに書き込むように Spring Data に指示します。

Spring コンバーターを使用した読み取り

次の例は、String から Boolean 値に変換する Converter の実装を示しています。

@ReadingConverter
public class StringToBooleanConverter implements Converter<String, Boolean> {

    @Override
    public Boolean convert(String source) {
        return source != null && source.equalsIgnoreCase("T") ? Boolean.TRUE : Boolean.FALSE;
    }
}

ここで注意すべき点がいくつかあります。String と Boolean はどちらも単純な型であるため、Spring Data はこのコンバーターを適用する方向(読み取りまたは書き込み)のヒントを必要とします。このコンバーターに @ReadingConverter のアノテーションを付けることにより、Spring Data に、Boolean プロパティに割り当てる必要があるデータベースからのすべての String 値を変換するように指示します。

JdbcConverter を使用した Spring コンバーターの登録

class MyJdbcConfiguration extends AbstractJdbcConfiguration {

    // …

    @Override
    protected List<?> userConverters() {
	return Arrays.asList(new BooleanToStringConverter(), new StringToBooleanConverter());
    }

}
以前のバージョンの Spring Data JDBC では、AbstractJdbcConfiguration.jdbcCustomConversions() を直接上書きすることが推奨されていました。この方法では、すべてのデータベースを対象とした変換、使用された Dialect によって登録された変換、ユーザーによって登録された変換がアセンブルされるため、これは不要になり、推奨もされなくなりました。古いバージョンの Spring Data JDBC から移行していて、AbstractJdbcConfiguration.jdbcCustomConversions() が上書きされた場合、Dialect からの変換は登録されません。

Spring Boot に依存して Spring Data JDBC をブートストラップしたいが、構成の特定の側面をオーバーライドしたい場合は、その型の Bean を公開するとよいでしょう。カスタム変換の場合は、次のようにすることができます。Boot インフラストラクチャによって選択される、型 JdbcCustomConversions の Bean を登録することを選択します。詳細については、"Spring Boot リファレンスドキュメント" を必ず参照してください。

JdbcValue

値変換では、JdbcValue を使用して、java.sql.Types 型の JDBC 操作に伝搬される値を強化します。型の派生を使用する代わりに JDBC 固有の型を指定する必要がある場合は、カスタム書き込みコンバーターを登録します。このコンバーターは、値を実際の JDBCType のフィールドを持つ JdbcValue に変換する必要があります。