最新の安定バージョンについては、Spring Data MongoDB 4.4.3 を使用してください!

オブジェクトマッピング

豊富なマッピングのサポートは、MappingMongoConverter によって提供されます。コンバーターは、ドメインオブジェクトを MongoDB ドキュメントにマップするための完全な機能セットを提供するメタデータモデルを保持します。マッピングメタデータモデルは、ドメインオブジェクトのアノテーションを使用して設定されます。ただし、インフラストラクチャは、メタデータ情報の唯一のソースとしてアノテーションを使用することに限定されません。MappingMongoConverter では、一連の規則に従って、追加のメタデータを提供せずにオブジェクトをドキュメントにマップすることもできます。

このセクションでは、基礎、オブジェクトをドキュメントにマッピングするための規則の使用方法、およびアノテーションベースのマッピングメタデータでそれらの規則をオーバーライドする方法を含む、MappingMongoConverter の機能について説明します。

オブジェクトマッピングの基礎

このセクションでは、Spring Data オブジェクトマッピング、オブジェクト作成、フィールドとプロパティへのアクセス、可変性と不変性の基礎について説明します。このセクションは、基になるデータストア(JPA など)のオブジェクトマッピングを使用しない Spring Data モジュールにのみ適用されることに注意してください。また、インデックス、列名やフィールド名のカスタマイズなど、ストア固有のオブジェクトマッピングについては、ストア固有のセクションを参照してください。

Spring Data オブジェクトマッピングの中心的なロールは、ドメインオブジェクトのインスタンスを作成し、ストアネイティブデータ構造をそれらにマッピングすることです。つまり、2 つの基本的な手順が必要です。

  1. 公開されたコンストラクターの 1 つを使用したインスタンスの作成。

  2. すべての公開されたプロパティを具体化するインスタンスの設定。

オブジェクト作成

Spring Data は、その型のオブジェクトの具体化に使用される永続エンティティのコンストラクターを自動的に検出しようとします。解決アルゴリズムは次のように機能します。

  1. @PersistenceCreator でアノテーションが付けられた単一の静的ファクトリメソッドがある場合は、それが使用されます。

  2. コンストラクターが 1 つしかない場合は、それが使用されます。

  3. 複数のコンストラクターがあり、そのうちの 1 つだけに @PersistenceCreator アノテーションが付けられている場合は、それが使用されます。

  4. 型が Java Record の場合、標準コンストラクターが使用されます。

  5. 引数のないコンストラクターがある場合は、それが使用されます。他のコンストラクターは無視されます。

値の解決では、コンストラクター / ファクトリメソッドの引数名がエンティティのプロパティ名と一致することを前提としています。つまり、マッピングのすべてのカスタマイズ(異なるデータストア列またはフィールド名など)を含め、プロパティが入力されたかのように解決が実行されます。これには、クラスファイルで利用可能なパラメーター名情報、またはコンストラクターに存在する @ConstructorProperties アノテーションも必要です。

値の解決は、ストア固有の SpEL 式を使用した Spring Framework の @Value 値アノテーションを使用してカスタマイズできます。詳細については、ストア固有のマッピングに関するセクションを参照してください。

オブジェクト作成の詳細

リフレクションのオーバーヘッドを回避するために、Spring Data オブジェクトの作成では、デフォルトで実行時に生成されるファクトリクラスを使用します。これにより、ドメインクラスコンストラクターが直接呼び出されます。つまりこの例の型:

class Person {
  Person(String firstname, String lastname) { … }
}

実行時にこれと意味的に同等のファクトリクラスを作成します。

class PersonObjectInstantiator implements ObjectInstantiator {

  Object newInstance(Object... args) {
    return new Person((String) args[0], (String) args[1]);
  }
}

これにより、反射よりも約 10% パフォーマンスが向上します。ドメインクラスがこのような最適化の対象となるには、一連の制約に従う必要があります。

  • プライベートクラスであってはなりません

  • 非静的内部クラスであってはなりません

  • CGLib プロキシクラスであってはなりません

  • Spring Data で使用されるコンストラクターはプライベートであってはなりません

これらの条件のいずれかが一致する場合、Spring Data はリフレクションを介してエンティティのインスタンス化にフォールバックします。

プロパティ設定

エンティティのインスタンスが作成されると、Spring Data はそのクラスの残りのすべての永続プロパティを設定します。エンティティのコンストラクターによってすでに入力されていない場合(つまり、コンストラクターの引数リストを介して使用される場合)、ID プロパティが最初に入力され、循環オブジェクト参照の解決が可能になります。その後、コンストラクターによってまだ設定されていないすべての非一時的なプロパティがエンティティインスタンスに設定されます。そのために、次のアルゴリズムを使用します。

  1. プロパティが不変であるが with …  メソッドを公開している場合(以下を参照)、with …  メソッドを使用して、新しいプロパティ値を持つ新しいエンティティインスタンスを作成します。

  2. プロパティアクセス(つまり、getter および setter を介したアクセス)が定義されている場合、setter メソッドを呼び出しています。

  3. プロパティが変更可能な場合、フィールドを直接設定します。

  4. プロパティが不変の場合、永続化操作(オブジェクト作成を参照)で使用されるコンストラクターを使用して、インスタンスのコピーを作成します。

  5. デフォルトでは、フィールド値を直接設定します。

プロパティ設定の詳細

オブジェクト構築の最適化と同様に、Spring Data ランタイム生成のアクセサークラスを使用して、エンティティインスタンスと対話します。

class Person {

  private final Long id;
  private String firstname;
  private @AccessType(Type.PROPERTY) String lastname;

  Person() {
    this.id = null;
  }

  Person(Long id, String firstname, String lastname) {
    // Field assignments
  }

  Person withId(Long id) {
    return new Person(id, this.firstname, this.lastame);
  }

  void setLastname(String lastname) {
    this.lastname = lastname;
  }
}
生成されたプロパティアクセサー
class PersonPropertyAccessor implements PersistentPropertyAccessor {

  private static final MethodHandle firstname;              (2)

  private Person person;                                    (1)

  public void setProperty(PersistentProperty property, Object value) {

    String name = property.getName();

    if ("firstname".equals(name)) {
      firstname.invoke(person, (String) value);             (2)
    } else if ("id".equals(name)) {
      this.person = person.withId((Long) value);            (3)
    } else if ("lastname".equals(name)) {
      this.person.setLastname((String) value);              (4)
    }
  }
}
1PropertyAccessor は、基礎となるオブジェクトの可変インスタンスを保持します。これは、そうでなければ不変のプロパティの変更を可能にするためです。
2 デフォルトでは、Spring Data はフィールドアクセスを使用してプロパティ値を読み書きします。private フィールドの可視性ルールに従って、MethodHandles はフィールドとの対話に使用されます。
3 クラスは、識別子の設定に使用される withId(…) メソッドを公開します。インスタンスがデータストアに挿入され、識別子が生成されたとき。withId(…) を呼び出すと、新しい Person オブジェクトが作成されます。後続のすべての変更は、新しいインスタンスで行われ、前のインスタンスは変更されません。
4property-access を使用すると、MethodHandles を使用せずに直接メソッドを呼び出すことができます。

これにより、反射よりも約 25% パフォーマンスが向上します。ドメインクラスがこのような最適化の対象となるには、一連の制約に従う必要があります。

  • 型は、デフォルトまたは java パッケージに存在してはなりません。

  • 型とそのコンストラクターは public でなければなりません

  • 内部クラスである型は static でなければなりません。

  • 使用される Java ランタイムは、元の ClassLoader でクラスを宣言できるようにする必要があります。Java 9 以降には特定の制限があります。

デフォルトでは、Spring Data は生成されたプロパティアクセサーを使用しようとし、制限が検出された場合はリフレクションベースのものにフォールバックします。

次のエンティティを見てみましょう。

サンプルエンティティ
class Person {

  private final @Id Long id;                                                (1)
  private final String firstname, lastname;                                 (2)
  private final LocalDate birthday;
  private final int age;                                                    (3)

  private String comment;                                                   (4)
  private @AccessType(Type.PROPERTY) String remarks;                        (5)

  static Person of(String firstname, String lastname, LocalDate birthday) { (6)

    return new Person(null, firstname, lastname, birthday,
      Period.between(birthday, LocalDate.now()).getYears());
  }

  Person(Long id, String firstname, String lastname, LocalDate birthday, int age) { (6)

    this.id = id;
    this.firstname = firstname;
    this.lastname = lastname;
    this.birthday = birthday;
    this.age = age;
  }

  Person withId(Long id) {                                                  (1)
    return new Person(id, this.firstname, this.lastname, this.birthday, this.age);
  }

  void setRemarks(String remarks) {                                         (5)
    this.remarks = remarks;
  }
}
1identifier プロパティは final ですが、コンストラクターで null に設定されます。クラスは、識別子の設定に使用される withId(…) メソッドを公開します。インスタンスがデータストアに挿入され、識別子が生成されたとき。元の Person インスタンスは、新しいインスタンスが作成されるときに変更されません。通常、ストア管理される他のプロパティにも同じパターンが適用されますが、永続化操作のために変更する必要がある場合があります。永続化コンストラクター(6 を参照)は事実上コピーコンストラクターであり、プロパティの設定は新しい識別子値が適用された新しいインスタンスの作成に変換されるため、wither メソッドはオプションです。
2firstname および lastname プロパティは、getter を介して潜在的に公開される通常の不変のプロパティです。
3age プロパティは不変ですが、birthday プロパティから派生しています。示されている設計では、Spring Data は宣言された唯一のコンストラクターを使用するため、データベース値はデフォルト設定よりも優先されます。計算が優先されることを意図している場合でも、このコンストラクターがパラメーターとして age を受け取ることが重要です(無視される可能性があります)。そうしないと、プロパティ生成ステップは age フィールドを設定しようとし、不変で no with …  メソッドが存在します。
4comment プロパティは変更可能で、そのフィールドを直接設定することによって入力されます。
5remarks プロパティは変更可能で、setter メソッドを呼び出すことによって設定されます。
6 このクラスは、オブジェクト作成用のファクトリメソッドとコンストラクターを公開します。ここでの中心的な考え方は、@PersistenceCreator によるコンストラクターの曖昧性解消の必要性を回避するために、追加のコンストラクターの代わりにファクトリメソッドを使用することです。代わりに、プロパティのデフォルト設定はファクトリメソッド内で処理されます。Spring Data でオブジェクトのインスタンス化にファクトリメソッドを使用する場合は、@PersistenceCreator でアノテーションを付けます。

一般的な推奨事項

  • 不変オブジェクトにこだわる — 不変オブジェクトは、オブジェクトを具体化するのはコンストラクターのみを呼び出すだけなので、簡単に作成できます。また、これにより、クライアントオブジェクトがオブジェクトの状態を操作できるようにする setter メソッドがドメインオブジェクトに散らばるのを防ぎます。それらが必要な場合は、同じ場所に配置された限られた型でのみ呼び出せるように、パッケージを保護することをお勧めします。コンストラクターのみの実体化は、プロパティの設定よりも最大 30% 高速です。

  • all-args コンストラクターを提供する  — エンティティを不変の値としてモデル化できない、またはしたくない場合でも、オブジェクトのマッピングがプロパティの設定をスキップできるため、エンティティのすべてのプロパティを引数として取るコンストラクターを提供することには価値があります。最適なパフォーマンスのため。

  • @PersistenceCreator を回避するために、オーバーロードされたコンストラクターの代わりにファクトリメソッドを使用します — 最適なパフォーマンスに必要なすべての引数コンストラクターでは、通常、自動生成識別子などを省略したアプリケーションユースケース固有のコンストラクターを公開します。これらの all-args コンストラクターのバリアントを公開する静的ファクトリメソッド。

  • 生成されたインスタンス生成クラスとプロパティアクセッサクラスを使用できるようにする制約を必ず守ってください。

  • 生成される識別子については、すべての引数の永続化コンストラクター(推奨)または with …  メソッドと組み合わせて final フィールドを使用します

  • Lombok を使用してボイラープレートコードを回避します — 永続化操作は通常、すべての引数を取るコンストラクターを必要とするため、その宣言はフィールド割り当てに対するボイラープレートパラメーターの退屈な繰り返しとなりますが、Lombok の @AllArgsConstructor を使用することで回避することができます。

プロパティのオーバーライド

Java を使用すると、ドメインクラスを柔軟に設計できます。この場合、サブクラスは、スーパークラスで同じ名前ですでに宣言されているプロパティを定義できます。次の例を考えてみましょう。

public class SuperType {

   private CharSequence field;

   public SuperType(CharSequence field) {
      this.field = field;
   }

   public CharSequence getField() {
      return this.field;
   }

   public void setField(CharSequence field) {
      this.field = field;
   }
}

public class SubType extends SuperType {

   private String field;

   public SubType(String field) {
      super(field);
      this.field = field;
   }

   @Override
   public String getField() {
      return this.field;
   }

   public void setField(String field) {
      this.field = field;

      // optional
      super.setField(field);
   }
}

どちらのクラスも、割り当て可能な型を使用して field を定義します。ただし、SubType は SuperType.field をシャドウします。クラスの設計によっては、コンストラクターを使用することが SuperType.field を設定するための唯一のデフォルトのアプローチである可能性があります。または、setter で super.setField(…) を呼び出すと、SuperType で field を設定できます。プロパティは同じ名前を共有しますが、2 つの異なる値を表す可能性があるため、これらすべてのメカニズムはある程度の競合を引き起こします。Spring Data は、型が割り当て可能でない場合、スーパー型のプロパティをスキップします。つまり、オーバーライドされたプロパティの型は、オーバーライドとして登録されるスーパー型のプロパティ型に割り当て可能である必要があります。そうでない場合、スーパー型のプロパティは一時的なものと見なされます。通常、個別のプロパティ名を使用することをお勧めします。

Spring Data モジュールは通常、異なる値を保持するオーバーライドされたプロパティをサポートします。プログラミングモデルの観点から、考慮すべきことがいくつかあります。

  1. どのプロパティを永続化する必要がありますか(デフォルトでは、宣言されたすべてのプロパティになります)? これらに @Transient アノテーションを付けることで、プロパティを除外できます。

  2. データストアのプロパティを表す方法は? 異なる値に同じフィールド / 列名を使用すると、通常、データが破損するため、明示的なフィールド / 列名を使用してプロパティの少なくとも 1 つにアノテーションを付ける必要があります。

  3. @AccessType(PROPERTY) を使用することは、通常、setter 実装のさらなる仮定を行わずにスーパープロパティを設定することができないため、使用できません。

Kotlin サポート

Spring Data は、Kotlin の仕様を適合させて、オブジェクトの作成と変更を可能にします。

Kotlin オブジェクトの作成

Kotlin クラスはインスタンス化がサポートされています。すべてのクラスはデフォルトで不変であり、変更可能なプロパティを定義するには明示的なプロパティ宣言が必要です。

Spring Data は、その型のオブジェクトの具体化に使用される永続エンティティのコンストラクターを自動的に検出しようとします。解決アルゴリズムは次のように機能します。

  1. @PersistenceCreator でアノテーションが付けられたコンストラクターがある場合は、それが使用されます。

  2. 型が Kotlin データクラスの場合、プライマリコンストラクターが使用されます。

  3. @PersistenceCreator でアノテーションが付けられた単一の静的ファクトリメソッドがある場合は、それが使用されます。

  4. コンストラクターが 1 つしかない場合は、それが使用されます。

  5. 複数のコンストラクターがあり、そのうちの 1 つだけに @PersistenceCreator アノテーションが付けられている場合は、それが使用されます。

  6. 型が Java Record の場合、標準コンストラクターが使用されます。

  7. 引数のないコンストラクターがある場合は、それが使用されます。他のコンストラクターは無視されます。

次の data クラス Person を検討してください。

data class Person(val id: String, val name: String)

上記のクラスは、明示的なコンストラクターを持つ典型的なクラスにコンパイルされます。別のコンストラクターを追加してこのクラスをカスタマイズし、@PersistenceCreator でアノテーションを付けてコンストラクターの設定を示します。

data class Person(var id: String, val name: String) {

    @PersistenceCreator
    constructor(id: String) : this(id, "unknown")
}

Kotlin は、パラメーターが提供されない場合にデフォルト値を使用できるようにすることで、パラメーターのオプションをサポートしています。Spring Data がパラメーターのデフォルト設定を持つコンストラクターを検出した場合、データストアが値を提供しない(または単に null を返す)場合、Kotlin はパラメーターのデフォルト設定を適用できるため、これらのパラメーターは存在しません。name のパラメーターのデフォルト設定を適用する次のクラスを検討してください。

data class Person(var id: String, val name: String = "unknown")

name パラメーターが結果の一部ではないか、その値が null であるたびに、name は unknown にデフォルト設定されます。

委譲されたプロパティは Spring Data ではサポートされていません。マッピングメタデータは、Kotlin データクラスの委譲されたプロパティをフィルターします。その他の場合は、プロパティに @delegate:org.springframework.data.annotation.Transient のアノテーションを付けることで、委譲されたプロパティの合成フィールドを除外できます。

Kotlin データクラスのプロパティ設定

Kotlin では、すべてのクラスはデフォルトで不変であり、可変プロパティを定義するには明示的なプロパティ宣言が必要です。次の data クラス Person を検討してください。

data class Person(val id: String, val name: String)

このクラスは事実上不変です。Kotlin が既存のオブジェクトからすべてのプロパティ値をコピーしてメソッドに引数として提供されたプロパティ値を適用する新しいオブジェクトインスタンスを作成する copy(…) メソッドを生成するときに、新しいインスタンスを作成できます。

Kotlin オーバーライドプロパティ

Kotlin では、プロパティのオーバーライド (英語) を宣言して、サブクラスのプロパティを変更できます。

open class SuperType(open var field: Int)

class SubType(override var field: Int = 1) :
	SuperType(field) {
}

このような配置では、field という名前の 2 つのプロパティがレンダリングされます。Kotlin は、各クラスの各プロパティのプロパティアクセサー(getter および setter)を生成します。事実上、コードは次のようになります。

public class SuperType {

   private int field;

   public SuperType(int field) {
      this.field = field;
   }

   public int getField() {
      return this.field;
   }

   public void setField(int field) {
      this.field = field;
   }
}

public final class SubType extends SuperType {

   private int field;

   public SubType(int field) {
      super(field);
      this.field = field;
   }

   public int getField() {
      return this.field;
   }

   public void setField(int field) {
      this.field = field;
   }
}

SubType の Getter および setter は、SubType.field のみを設定し、SuperType.field は設定しません。このような配置では、コンストラクターを使用することが SuperType.field を設定するための唯一のデフォルトのアプローチです。SubType にメソッドを追加して this.SuperType.field = …  を介して SuperType.field を設定することは可能ですが、サポートされている規則の範囲外です。プロパティは同じ名前を共有しますが、2 つの異なる値を表す可能性があるため、プロパティのオーバーライドによってある程度の競合が発生します。通常、個別のプロパティ名を使用することをお勧めします。

Spring Data モジュールは通常、異なる値を保持するオーバーライドされたプロパティをサポートします。プログラミングモデルの観点から、考慮すべきことがいくつかあります。

  1. どのプロパティを永続化する必要がありますか(デフォルトでは、宣言されたすべてのプロパティになります)? これらに @Transient アノテーションを付けることで、プロパティを除外できます。

  2. データストアのプロパティを表す方法は? 異なる値に同じフィールド / 列名を使用すると、通常、データが破損するため、明示的なフィールド / 列名を使用してプロパティの少なくとも 1 つにアノテーションを付ける必要があります。

  3. @AccessType(PROPERTY) を使用すると、スーパープロパティが設定できないため使用できません。

Kotlin 値クラス

Kotlin 値クラスは、基礎となる概念を明示するために、より表現力豊かなドメインモデル用に設計されています。Spring Data は、値クラスを使用してプロパティを定義する型の読み取りと書き込みができます。

次のドメインモデルを検討してください。

@JvmInline
value class EmailAddress(val theAddress: String)                                    (1)

data class Contact(val id: String, val name:String, val emailAddress: EmailAddress) (2)
1Null 非許容値型を持つ単純な値クラス。
2EmailAddress 値クラスを使用してプロパティを定義するデータクラス。
非プリミティブ値型を使用する null 非許容プロパティは、コンパイルされたクラスで値型にフラット化されます。Null 許容プリミティブ値型または Null 許容値内値型は、ラッパー型で表現され、データベース内での値型の表現方法に影響します。

規約ベースのマッピング

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

  • 短い Java クラス名は、次の方法でコレクション名にマップされます。クラス com.bigbank.SavingsAccount は savingsAccount コレクション名にマップされます。

  • すべてのネストされたオブジェクトは、DBRef としてではなく、ネストされたオブジェクトとしてドキュメントに保存されます。

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

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

  • 引数ゼロ以外のコンストラクターが 1 つあり、そのコンストラクター引数名がドキュメントのトップレベルのフィールド名と一致する場合は、そのコンストラクターが使用されます。それ以外の場合は、引数ゼロのコンストラクターが使用されます。引数がゼロではないコンストラクターが複数ある場合、例外がスローされます。

_id フィールドがマッピング層でどのように処理されるか。

MongoDB では、すべてのドキュメントに _id フィールドが必要です。フィールドを指定しない場合は、ドライバーが生成された値で ObjectId を割り当てます。_id フィールドは、一意であれば、配列以外の任意の型にすることができます。ドライバーは当然、すべてのプリミティブ型と MappingMongoConverter を使用する Dates.When をサポートしますが、Java クラスのプロパティが _id フィールドにマップされる方法を規定する特定のルールがあります。

以下に、どのフィールドが _id ドキュメントフィールドにマップされるかを概説します。

  • @Id (org.springframework.data.annotation.Id) でアノテーションが付けられたフィールドは、_id フィールドにマップされます。
    さらに、ドキュメントフィールドの名前は @Field アノテーションを使用してカスタマイズできます。この場合、ドキュメントにはフィールド _id は含まれません。

  • アノテーションのない id という名前のフィールドは、_id フィールドにマップされます。

表 1: _id フィールド定義の変換の例
フィールドの定義 MongoDB の結果の ID- フィールド名

String id

_id

@Field String id

_id

@Field("x") String id

x

@Id String x

_id

@Field("x") @Id String y

_id (@Field(name) is ignored, @Id takes precedence)

The following outlines what type conversion, if any, will be done on the property mapped to the _id document field.

  • If a field named id is declared as a String or BigInteger in the Java class it will be converted to and stored as an ObjectId if possible. ObjectId as a field type is also valid. If you specify a value for id in your application, the conversion to an ObjectId is done by the MongoDB driver. If the specified id value cannot be converted to an ObjectId, then the value will be stored as is in the document’s _id field. This also applies if the field is annotated with @Id.

  • If a field is annotated with @MongoId in the Java class it will be converted to and stored as using its actual type. No further conversion happens unless @MongoId declares a desired field type. If no value is provided for the id field, a new ObjectId will be created and converted to the properties type.

  • If a field is annotated with @MongoId(FieldType.…) in the Java class it will be attempted to convert the value to the declared FieldType. If no value is provided for the id field, a new ObjectId will be created and converted to the declared type.

  • If a field named id is not declared as a String, BigInteger, or ObjectID in the Java class then you should assign it a value in your application so it can be stored 'as-is' in the document’s _id field.

  • If no field named id is present in the Java class then an implicit _id file will be generated by the driver but not mapped to a property or field of the Java class.

When querying and updating MongoTemplate will use the converter to handle conversions of the Query and Update objects that correspond to the above rules for saving documents so field names and types used in your queries will be able to match what is in your domain classes.

Data Mapping and Type Conversion

Spring Data MongoDB supports all types that can be represented as BSON, MongoDB’s internal document format. In addition to these types, Spring Data MongoDB provides a set of built-in converters to map additional types. You can provide your own converters to adjust type conversion. See Custom Conversions - Overriding Default Mapping for further details.

Built in Type conversions:
Table 2. Type
Type Type conversion Sample

String

ネイティブ

{"firstname" : "Dave"}

double, Double, float, Float

ネイティブ

{"weight" : 42.5}

int, Integer, short, Short

ネイティブ
32 ビット整数

{"height" : 42}

long, Long

ネイティブ
64 ビット整数

{"height" : 42}

Date, Timestamp

ネイティブ

{"date" : ISODate("2019-11-12T23:00:00.809Z")}

byte[]

ネイティブ

{"bin" : { "$binary" : "AQIDBA==", "$type" : "00" }}

java.util.UUID (Legacy UUID)

native

{"uuid" : { "$binary" : "MEaf1CFQ6lSphaa3b9AtlA==", "$type" : "03" }}

Date

ネイティブ

{"date" : ISODate("2019-11-12T23:00:00.809Z")}

ObjectId

ネイティブ

{"_id" : ObjectId("5707a2690364aba3136ab870")}

配列、ListBasicDBList

ネイティブ

{"cookies" : [ … ]}

boolean, Boolean

ネイティブ

{"active" : true}

null

ネイティブ

{"value" : null}

Document

ネイティブ

{"value" : { … }}

Decimal128

ネイティブ

{"value" : NumberDecimal(…)}

AtomicInteger
calling get() before the actual conversion

converter
32-bit integer

{"value" : "741" }

AtomicLong
calling get() before the actual conversion

converter
64-bit integer

{"value" : "741" }

BigInteger

コンバーター
String

{"value" : "741" }

BigDecimal

コンバーター
String

{"value" : "741.99" }

URL

コンバーター

{"website" : "https://spring.io/projects/spring-data-mongodb/" }

Locale

コンバーター

{"locale : "en_US" }

char, Character

コンバーター

{"char" : "a" }

NamedMongoScript

コンバーター
Code

{"_id" : "script name", value: (some javascript code)}

java.util.Currency

コンバーター

{"currencyCode" : "EUR"}

Instant
(Java 8)

native

{"date" : ISODate("2019-11-12T23:00:00.809Z")}

Instant
(Joda, JSR310-BackPort)

converter

{"date" : ISODate("2019-11-12T23:00:00.809Z")}

LocalDate
(Joda, Java 8, JSR310-BackPort)

converter / native (Java8)[1]

{"date" : ISODate("2019-11-12T00:00:00.000Z")}

LocalDateTime, LocalTime
(Joda, Java 8, JSR310-BackPort)

converter / native (Java8)[2]

{"date" : ISODate("2019-11-12T23:00:00.809Z")}

DateTime (Joda)

converter

{"date" : ISODate("2019-11-12T23:00:00.809Z")}

ZoneId (Java 8, JSR310-BackPort)

converter

{"zoneId" : "ECT - Europe/Paris"}

Box

コンバーター

{"box" : { "first" : { "x" : 1.0 , "y" : 2.0} , "second" : { "x" : 3.0 , "y" : 4.0}}

Polygon

コンバーター

{"polygon" : { "points" : [ { "x" : 1.0 , "y" : 2.0} , { "x" : 3.0 , "y" : 4.0} , { "x" : 4.0 , "y" : 5.0}]}}

Circle

コンバーター

{"circle" : { "center" : { "x" : 1.0 , "y" : 2.0} , "radius" : 3.0 , "metric" : "NEUTRAL"}}

Point

コンバーター

{"point" : { "x" : 1.0 , "y" : 2.0}}

GeoJsonPoint

コンバーター

{"point" : { "type" : "Point" , "coordinates" : [3.0 , 4.0] }}

GeoJsonMultiPoint

コンバーター

{"geoJsonLineString" : {"type":"MultiPoint", "coordinates": [ [ 0 , 0 ], [ 0 , 1 ], [ 1 , 1 ] ] }}

Sphere

コンバーター

{"sphere" : { "center" : { "x" : 1.0 , "y" : 2.0} , "radius" : 3.0 , "metric" : "NEUTRAL"}}

GeoJsonPolygon

コンバーター

{"polygon" : { "type" : "Polygon", "coordinates" : [[ [ 0 , 0 ], [ 3 , 6 ], [ 6 , 1 ], [ 0 , 0 ] ]] }}

GeoJsonMultiPolygon

コンバーター

{"geoJsonMultiPolygon" : { "type" : "MultiPolygon", "coordinates" : [ [ [ [ -73.958 , 40.8003 ] , [ -73.9498 , 40.7968 ] ] ], [ [ [ -73.973 , 40.7648 ] , [ -73.9588 , 40.8003 ] ] ] ] }}

GeoJsonLineString

コンバーター

{ "geoJsonLineString" : { "type" : "LineString", "coordinates" : [ [ 40 , 5 ], [ 41 , 6 ] ] }}

GeoJsonMultiLineString

コンバーター

{"geoJsonLineString" : { "type" : "MultiLineString", coordinates: [ [ [ -73.97162 , 40.78205 ], [ -73.96374 , 40.77715 ] ], [ [ -73.97880 , 40.77247 ], [ -73.97036 , 40.76811 ] ] ] }}

コレクションの処理

コレクションの処理は、MongoDB によって返される実際の値に応じて異なります。

  • ドキュメントにコレクションにマップされたフィールドが含まれていない場合、マッピングによってプロパティは更新されません。つまり、値は null、java デフォルト、またはオブジェクト作成時に設定された値のままになります。

  • ドキュメントにマップされるフィールドが含まれているが、そのフィールドが null 値 ( { 'list' : null } など) を保持している場合、プロパティ値は null に設定されます。

  • ドキュメントに、null ではないコレクション ( { 'list' : [ …​ ] } など) にマップされるフィールドが含まれている場合、そのコレクションにはマップされた値が設定されます。

通常、コンストラクターの作成を使用すると、設定する値を取得できます。プロパティ値がクエリレスポンスによって提供されない場合、プロパティの作成ではデフォルトの初期化値を使用できます。

マッピング設定

明示的に構成しない限り、MongoTemplate を作成すると、デフォルトで MappingMongoConverter のインスタンスが作成されます。MappingMongoConverter の独自のインスタンスを作成できます。これにより、クラスパス内のドメインクラスが見つかる場所を指定できるようになり、Spring Data MongoDB がメタデータを抽出してインデックスを構築できるようになります。また、独自のインスタンスを作成することにより、Spring コンバーターを登録して、データベースとの間で特定のクラスをマップすることができます。

Java ベースまたは XML ベースのメタデータを使用して、MappingMongoConvertercom.mongodb.client.MongoClient、MongoTemplate を構成できます。次の例は構成を示しています。

  • Java

  • XML

@Configuration
public class MongoConfig extends AbstractMongoClientConfiguration {

  @Override
  public String getDatabaseName() {
    return "database";
  }

  // the following are optional

  @Override
  public String getMappingBasePackage() { (1)
    return "com.bigbank.domain";
  }

  @Override
  void configureConverters(MongoConverterConfigurationAdapter adapter) { (2)

  	adapter.registerConverter(new org.springframework.data.mongodb.test.PersonReadConverter());
  	adapter.registerConverter(new org.springframework.data.mongodb.test.PersonWriteConverter());
  }

  @Bean
  public LoggingEventListener<MongoMappingEvent> mappingEventsListener() {
    return new LoggingEventListener<MongoMappingEvent>();
  }
}
1 マッピング基本パッケージは、MappingContext の事前初期化に使用されるエンティティのスキャンに使用されるルートパスを定義します。デフォルトでは、構成クラスパッケージが使用されます。
2 特定のドメイン型に対して追加のカスタムコンバーターを構成し、それらの型のデフォルトのマッピング手順をカスタム実装に置き換えます。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:mongo="http://www.springframework.org/schema/data/mongo"
  xsi:schemaLocation="
    http://www.springframework.org/schema/data/mongo https://www.springframework.org/schema/data/mongo/spring-mongo.xsd
    http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

  <!-- Default bean name is 'mongo' -->
  <mongo:mongo-client host="localhost" port="27017"/>

  <mongo:db-factory dbname="database" mongo-ref="mongoClient"/>

  <!-- by default look for a Mongo object named 'mongo' - default name used for the converter is 'mappingConverter' -->
  <mongo:mapping-converter base-package="com.bigbank.domain">
    <mongo:custom-converters>
      <mongo:converter ref="readConverter"/>
      <mongo:converter>
        <bean class="org.springframework.data.mongodb.test.PersonWriteConverter"/>
      </mongo:converter>
    </mongo:custom-converters>
  </mongo:mapping-converter>

  <bean id="readConverter" class="org.springframework.data.mongodb.test.PersonReadConverter"/>

  <!-- set the mapping converter to be used by the MongoTemplate -->
  <bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
    <constructor-arg name="mongoDbFactory" ref="mongoDbFactory"/>
    <constructor-arg name="mongoConverter" ref="mappingConverter"/>
  </bean>

  <bean class="org.springframework.data.mongodb.core.mapping.event.LoggingEventListener"/>

</beans>

AbstractMongoClientConfiguration では、com.mongodb.client.MongoClient を定義し、データベース名を指定するメソッドを実装する必要があります。AbstractMongoClientConfiguration には、getMappingBasePackage(…) という名前のメソッドもあり、これをオーバーライドして、@Document アノテーションが付けられたクラスをスキャンする場所をコンバーターに指示できます。

customConversionsConfiguration メソッドをオーバーライドすることで、コンバーターにコンバーターを追加できます。MongoDB のネイティブ JSR-310 サポートは、MongoConverterConfigurationAdapter.useNativeDriverJavaTimeCodecs() を通じて有効にすることができます。前の例には、Spring の ApplicationContextEvent インフラストラクチャにポストされる MongoMappingEvent インスタンスをログに記録する LoggingEventListener も示されています。

Java 時間の種類

MongoDB は UTC ベースのアプローチを使用しているため、上記のように MongoConverterConfigurationAdapter.useNativeDriverJavaTimeCodecs() 経由で MongoDB のネイティブ JSR-310 サポートを使用することをお勧めします。Spring Data Commons から継承された java.time 型のデフォルトの JSR-310 サポートは、ローカルマシンのタイムゾーンを参照として使用するため、下位互換性の目的でのみ使用する必要があります。

AbstractMongoClientConfiguration は MongoTemplate インスタンスを作成し、それを mongoTemplate という名前でコンテナーに登録します。

base-package プロパティは、@org.springframework.data.mongodb.core.mapping.Document アノテーションが付けられたクラスをスキャンする場所を指定します。

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

メタデータベースのマッピング

Spring Data MongoDB サポート内のオブジェクトマッピング機能を最大限に活用するには、マップされたオブジェクトに @Document アノテーションを付ける必要があります。マッピングフレームワークにこのアノテーションを付ける必要はありませんが(アノテーションがなくても POJO は正しくマッピングされます)、クラスパススキャナーでドメインオブジェクトを見つけて前処理し、必要なメタデータを抽出できます。このアノテーションを使用しない場合、マッピングフレームワークは、ドメインオブジェクトのプロパティとそのメソッドを認識できるように内部メタデータモデルを構築する必要があるため、ドメインオブジェクトを最初に保存するときにアプリケーションのパフォーマンスがわずかに低下します。永続化します。次の例は、ドメインオブジェクトを示しています。

例 1: ドメインオブジェクトの例
package com.mycompany.domain;

@Document
public class Person {

  @Id
  private ObjectId id;

  @Indexed
  private Integer ssn;

  private String firstName;

  @Indexed
  private String lastName;
}
@Id アノテーションは、MongoDB _id プロパティにどのプロパティを使用するかをマッパーに伝え、@Indexed アノテーションは、ドキュメントのそのプロパティに対して createIndex(…) を呼び出すようにマッピングフレームワークに指示し、検索を高速化します。自動インデックス作成は、@Document アノテーションが付けられた型に対してのみ実行されます。
自動インデックス作成はデフォルトでは無効になっており、構成を通じて有効にする必要があります ( インデックスの作成を参照)。

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

MappingMongoConverter はメタデータを使用して、オブジェクトのドキュメントへのマッピングを実行できます。次のアノテーションが利用可能です。

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

  • @MongoId: ID 目的に使用されるフィールドをマークするためにフィールドレベルで適用されます。オプションの FieldType を受け入れて ID 変換をカスタマイズします。

  • @Document: クラスレベルで適用され、このクラスがデータベースへのマッピングの候補であることを示します。データが保存されるコレクションの名前を指定できます。

  • @DBRef: com.mongodb.DBRef を使用して保存されることを示すためにフィールドに適用されます。

  • @DocumentReference: 別のドキュメントへのポインターとして保存されることを示すためにフィールドに適用されます。これは単一の値 (デフォルトでは ID ) にすることも、コンバーターを介して提供される Document にすることもできます。

  • @Indexed: フィールドのインデックス付け方法を説明するためにフィールドレベルで適用されます。

  • @CompoundIndex (繰り返し可能な): 複合インデックスを宣言するために型レベルで適用されます。

  • @GeoSpatialIndexed: フィールドの地理インデックスを作成する方法を説明するためにフィールドレベルで適用されます。

  • @TextIndexed: テキストインデックスに含めるフィールドをマークするためにフィールドレベルで適用されます。

  • @HashIndexed: シャードクラスター全体でデータを分割するために、ハッシュインデックス内で使用するためにフィールドレベルで適用されます。

  • @Language: テキストインデックスの言語オーバーライドプロパティを設定するためにフィールドレベルで適用されます。

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

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

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

  • @Field: フィールドレベルで適用すると、MongoDB BSON ドキュメントで表現されるフィールドの名前と型を記述することができるため、名前と型をクラスのフィールド名やプロパティの型とは異なるものにすることができます。

  • @Version: フィールドレベルで適用されると、オプティミスティックロックに使用され、保存操作時に変更がチェックされます。初期値は zero (プリミティブ型の場合は one ) で、更新のたびに自動的にバンプされます。

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

より複雑なマッピングの例を次に示します。
@Document
@CompoundIndex(name = "age_idx", def = "{'lastName': 1, 'age': -1}")
public class Person<T extends Address> {

  @Id
  private String id;

  @Indexed(unique = true)
  private Integer ssn;

  @Field("fName")
  private String firstName;

  @Indexed
  private String lastName;

  private Integer age;

  @Transient
  private Integer accountTotal;

  @DBRef
  private List<Account> accounts;

  private T address;

  public Person(Integer ssn) {
    this.ssn = ssn;
  }

  @PersistenceConstructor
  public Person(Integer ssn, String firstName, String lastName, Integer age, T address) {
    this.ssn = ssn;
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
    this.address = address;
  }

  public String getId() {
    return id;
  }

  // no setter for Id.  (getter is only exposed for some unit testing)

  public Integer getSsn() {
    return ssn;
  }

// other getters/setters omitted
}

@Field(targetType=…​) は、マッピングインフラストラクチャによって推論されたネイティブ MongoDB 型が予想される型と一致しない場合に役立ちます。BigDecimal と同様に、MongoDB Server の以前のバージョンではサポートされていなかったため、Decimal128 ではなく String として表されます。

public class Balance {

  @Field(targetType = DECIMAL128)
  private BigDecimal value;

  // ...
}

独自のカスタムアノテーションを検討することもできます。

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Field(targetType = FieldType.DECIMAL128)
public @interface Decimal128 { }

// ...

public class Balance {

  @Decimal128
  private BigDecimal value;

  // ...
}

特殊なフィールド名

一般に、MongoDB は、ネストされたドキュメントまたは配列のパス区切り文字としてドット (.) 文字を使用します。これは、クエリ (または更新ステートメント) で、a.b.c のようなキーが以下に示すオブジェクト構造をターゲットにすることを意味します。

{
    'a' : {
        'b' : {
            'c' : …
        }
    }
}

MongoDB 5.0 フィールド名まではドット (.) を含めてはなりません。
 MappingMongoConverter#setMapKeyDotReplacement を使用すると、書き込み時のドットを別の文字に置き換えることで、Map 構造体を格納する際の制限の一部を回避できます。

converter.setMapKeyDotReplacement("-");
// ...

source.map = Map.of("key.with.dot", "value")
converter.write(source,...) // -> map : { 'key-with-dot', 'value' }

MongoDB 5.0 のリリースにより、特殊文字を含む Document フィールド名に対するこの制限は解除されました。MongoDB リファレンス (英語) のフィールド名でのドットの使用に関する制限について詳しく読むことを強くお勧めします。
 Map 構造でドットを許可するには、MappingMongoConverter で preserveMapKeys を設定してください。

@Field を使用すると、2 つの方法でドットを考慮するようにフィールド名をカスタマイズできます。

  1. @Field(name = "a.b"): 名前はパスとみなされます。操作では、{ a : { b : … } } などのネストされたオブジェクトの構造が想定されます。

  2. @Field(name = "a.b", fieldNameType = KEY): 名前はそのままの名前とみなされます。操作では、{ 'a.b' : … .. } として指定された値を持つフィールドが必要です

MongoDB クエリと更新ステートメントの両方におけるドット文字の特殊な性質により、ドットを含むフィールド名は直接ターゲットにすることができないため、派生クエリメソッドでの使用から除外されます。次の Item には、cat.id という名前のフィールドにマップされた categoryId プロパティがあるとします。

public class Item {

	@Field(name = "cat.id", fieldNameType = KEY)
	String categoryId;

	// ...
}

その生の表現は次のようになります

{
    'cat.id' : "5b28b5e7-52c2",
    ...
}

cat.id フィールドを直接ターゲットにすることはできないため (これはパスとして解釈されるため)、集約フレームワークの助けが必要です。

名前にドットが含まれるクエリフィールド
template.query(Item.class)
    // $expr : { $eq : [ { $getField : { input : '$$CURRENT', 'cat.id' }, '5b28b5e7-52c2' ] }
    .matching(expr(ComparisonOperators.valueOf(ObjectOperators.getValueOf("value")).equalToValue("5b28b5e7-52c2"))) (1)
    .all();
1 マッピング層は、プロパティ名 value を実際のフィールド名に変換します。ここでもターゲットフィールド名を使用することは絶対に有効です。
名前にドットが含まれるフィールドを更新します
template.update(Item.class)
    .matching(where("id").is("r2d2"))
    // $replaceWith: { $setField : { input: '$$CURRENT', field : 'cat.id', value : 'af29-f87f4e933f97' } }
    .apply(AggregationUpdate.newUpdate(ReplaceWithOperation.replaceWithValue(ObjectOperators.setValueTo("value", "af29-f87f4e933f97")))) (1)
    .first();
1 マッピング層は、プロパティ名 value を実際のフィールド名に変換します。ここでもターゲットフィールド名を使用することは絶対に有効です。

上記は、特別なフィールドが最上位のドキュメントレベルに存在する簡単な例を示しています。ネストのレベルが増加すると、フィールドとの対話に必要な集計式の複雑さが増加します。

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

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

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

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

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

class OrderItem {

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

  OrderItem(String id, @Value("#root.qty ?: 0") int quantity, double unitPrice) {
    this.id = id;
    this.quantity = quantity;
    this.unitPrice = unitPrice;
  }

  // getters/setters ommitted
}

Document input = new Document("id", "4711");
input.put("unitPrice", 2.5);
input.put("qty",5);
OrderItem item = converter.read(OrderItem.class, input);
指定されたプロパティパスを解決できない場合、quantity パラメーターの @Value アノテーション内の SpEL 式は値 0 に戻ります。

@PersistenceConstructor アノテーションを使用するための追加の例は、MappingMongoConverterUnitTests [GitHub] (英語) テストスイートにあります。

フレームワークイベントのマッピング

イベントは、マッピングプロセスのライフサイクル全体を通じて発生します。これについては、ライフサイクルイベントセクションで説明します。

Spring ApplicationContext でこれらの Bean を宣言すると、イベントが送出されるたびにそれらの Bean が呼び出されます。


1. UTC ゾーンオフセットを使用します。MongoConverterConfigurationAdapter 経由で設定する
2. UTC ゾーンオフセットを使用します。MongoConverterConfigurationAdapter 経由で設定する