アンラップ型

ラップされていないエンティティは、プロパティが親の MongoDB ドキュメントにフラット化される Java ドメインモデル内の値オブジェクトを設計するために使用されます。

アンラップされた型のマッピング

User.name に @Unwrapped のアノテーションが付けられている次のドメインモデルを考えてみましょう。@Unwrapped アノテーションは、UserName のすべてのプロパティを name プロパティを所有する user ドキュメントにフラット化する必要があることを示します。

例 1: オブジェクトのラップ解除のサンプルコード
class User {

    @Id
    String userId;

    @Unwrapped(onEmpty = USE_NULL) (1)
    UserName name;
}

class UserName {

    String firstname;

    String lastname;

}
{
  "_id" : "1da2ba06-3ba7",
  "firstname" : "Emma",
  "lastname" : "Frost"
}
1name プロパティをロードするときに、firstname と lastname の両方が null であるか存在しない場合、その値は null に設定されます。onEmpty=USE_EMPTY を使用すると、プロパティに潜在的な null 値を含む空の UserName が作成されます。

冗長性の低い埋め込み可能な型宣言の場合は、@Unwrapped(onEmpty = USE_NULL) および @Unwrapped(onEmpty = USE_EMPTY) の代わりに @Unwrapped.Nullable および @Unwrapped.Empty を使用します。どちらのアノテーションも、null 可能性 インスペクションを支援するために、JSR-305 @javax.annotation.Nonnull でメタアノテーションが付けられています。

ラップされていないオブジェクト内で複合型を使用することができます。ただし、ラップされていないフィールド自体を含めることはできません。

アンラップされた型のフィールド名

値オブジェクトは、@Unwrapped アノテーションのオプションの prefix 属性を使用して複数回ラップ解除できます。これを実行すると、選択したプレフィックスがラップ解除されたオブジェクト内の各プロパティまたは @Field(" … ") 名の前に付加されます。複数のプロパティが同じフィールド名にレンダリングされる場合、値は互いに上書きされることに注意してください。

例 2: 名前接頭辞を含むラップされていないオブジェクトのサンプルコード
class User {

    @Id
    String userId;

    @Unwrapped.Nullable(prefix = "u_") (1)
    UserName name;

    @Unwrapped.Nullable(prefix = "a_") (2)
    UserName name;
}

class UserName {

    String firstname;

    String lastname;
}
{
  "_id" : "a6a805bd-f95f",
  "u_firstname" : "Jean",             (1)
  "u_lastname" : "Grey",
  "a_firstname" : "Something",        (2)
  "a_lastname" : "Else"
}
1UserName のすべてのプロパティには、接頭辞 u_ が付きます。
2UserName のすべてのプロパティには、接頭辞 a_ が付きます。

まったく同じプロパティ上で @Field アノテーションと @Unwrapped を組み合わせることは意味をなさないため、エラーが発生します。アンラップされた型のプロパティに対して @Field を使用することは、完全に有効なアプローチです。

例 3: @Field アノテーションを持つオブジェクトのラップを解除するサンプルコード
public class User {

	@Id
    private String userId;

    @Unwrapped.Nullable(prefix = "u-") (1)
    UserName name;
}

public class UserName {

	@Field("first-name")              (2)
    private String firstname;

	@Field("last-name")
    private String lastname;
}
{
  "_id" : "2647f7b9-89da",
  "u-first-name" : "Barbara",         (2)
  "u-last-name" : "Gordon"
}
1UserName のすべてのプロパティには、接頭辞 u- が付きます。
2 最終的なフィールド名は、@Unwrapped(prefix) と @Field(name) を連結した結果です。

ラップされていないオブジェクトに対するクエリ

提供された Criteria がドメイン型と照合されるため、ラップされていないプロパティに対するクエリの定義は、型 レベルでもフィールドレベルでも可能です。実際のクエリをレンダリングするときに、プレフィックスと潜在的なカスタムフィールド名が考慮されます。以下のサンプルに示すように、ラップされていないオブジェクトのプロパティ名を使用して、含まれるすべてのフィールドと照合します。

例 4: ラップされていないオブジェクトに対するクエリ
UserName userName = new UserName("Carol", "Danvers")
Query findByUserName = query(where("name").is(userName));
User user = template.findOne(findByUserName, User.class);
db.collection.find({
  "firstname" : "Carol",
  "lastname" : "Danvers"
})

以下のスニペットに示すように、そのプロパティ名を使用して、ラップされていないオブジェクトの任意のフィールドを直接アドレス指定することもできます。

例 5: アンラップされたオブジェクトのフィールドに対するクエリ
Query findByUserFirstName = query(where("name.firstname").is("Shuri"));
List<User> users = template.findAll(findByUserFirstName, User.class);
db.collection.find({
  "firstname" : "Shuri"
})

ラップされていないフィールドで並べ替えます。

以下のサンプルに示すように、ラップされていないオブジェクトのフィールドは、プロパティパスを介して並べ替えに使用できます。

例 6: ラップされていないフィールドで並べ替える
Query findByUserLastName = query(where("name.lastname").is("Romanoff"));
List<User> user = template.findAll(findByUserName.withSort(Sort.by("name.firstname")), User.class);
db.collection.find({
  "lastname" : "Romanoff"
}).sort({ "firstname" : 1 })

可能ではありますが、ラップされていないオブジェクト自体を並べ替え条件として使用すると、そのすべてのフィールドが予測できない順序で含まれるため、順序が不正確になる可能性があります。

アンラップされたオブジェクトへのフィールド射影

以下のサンプルに示すように、ラップされていないオブジェクトのフィールドは、全体として、または単一のフィールドを介して射影の対象にすることができます。

例 7: ラップされていないオブジェクトに投影します。
Query findByUserLastName = query(where("name.firstname").is("Gamora"));
findByUserLastName.fields().include("name");                             (1)
List<User> user = template.findAll(findByUserName, User.class);
db.collection.find({
  "lastname" : "Gamora"
},
{
  "firstname" : 1,
  "lastname" : 1
})
1 アンラップされたオブジェクトのフィールド射影には、そのすべてのプロパティが含まれます。
例 8: ラップされていないオブジェクトのフィールドに投影します。
Query findByUserLastName = query(where("name.lastname").is("Smoak"));
findByUserLastName.fields().include("name.firstname");                   (1)
List<User> user = template.findAll(findByUserName, User.class);
db.collection.find({
  "lastname" : "Smoak"
},
{
  "firstname" : 1
})
1 アンラップされたオブジェクトのフィールド射影には、そのすべてのプロパティが含まれます。

ラップされていないオブジェクトに対する例示による問い合わせ。

ラップされていないオブジェクトは、他の型と同様に Example プローブ内で使用できます。この機能の詳細については、例示による問い合わせセクションを参照してください。

ラップされていないオブジェクトに対するリポジトリクエリ。

Repository 抽象化により、ラップされていないオブジェクトのフィールドおよびオブジェクト全体に対するクエリを派生できます。

例 9: ラップされていないオブジェクトに対するリポジトリクエリ。
interface UserRepository extends CrudRepository<User, String> {

	List<User> findByName(UserName username);         (1)

	List<User> findByNameFirstname(String firstname); (2)
}
1 ラップされていないオブジェクトのすべてのフィールドと照合します。
2firstname と一致します。

リポジトリ create-query-indexes 名前空間属性が true に設定されている場合でも、ラップされていないオブジェクトのインデックス作成は一時停止されます。

ラップされていないオブジェクトの更新

ラップされていないオブジェクトは、ドメインモデルの一部である他のオブジェクトと同様に更新できます。マッピングレイヤーは、構造を周囲に平坦化する処理を行います。以下の例に示すように、ラップされていないオブジェクトの単一の属性だけでなく、値全体も更新できます。

例 10: ラップされていないオブジェクトの単一フィールドを更新します。
Update update = new Update().set("name.firstname", "Janet");
template.update(User.class).matching(where("id").is("Wasp"))
   .apply(update).first()
db.collection.update({
  "_id" : "Wasp"
},
{
  "$set" { "firstname" : "Janet" }
},
{ ... }
)
例 11: ラップされていないオブジェクトを更新します。
Update update = new Update().set("name", new Name("Janet", "van Dyne"));
template.update(User.class).matching(where("id").is("Wasp"))
   .apply(update).first()
db.collection.update({
  "_id" : "Wasp"
},
{
  "$set" {
    "firstname" : "Janet",
    "lastname" : "van Dyne",
  }
},
{ ... }
)

ラップされていないオブジェクトの集計

集約フレームワークは、型付き集計のラップされていない値のマップを試みます。いずれかの値を参照する場合は、ラッパーオブジェクトを含むプロパティパスを使用してください。それ以外は特別な操作は必要ありません。

ラップされていないオブジェクトのインデックス

通常のオブジェクトの場合と同様に、ラップされていない型のプロパティに @Indexed アノテーションを付けることができます。所有プロパティで @Indexed を @Unwrapped アノテーションと一緒に使用することはできません。

public class User {

	@Id
    private String userId;

    @Unwrapped(onEmpty = USE_NULL)
    UserName name;                    (1)

    // Invalid -> InvalidDataAccessApiUsageException
    @Indexed                          (2)
    @Unwrapped(onEmpty = USE_Empty)
    Address address;
}

public class UserName {

    private String firstname;

    @Indexed
    private String lastname;           (1)
}
1users コレクション内の lastname に対して作成されたインデックス。
2@Indexed と @Unwrapped の併用が無効です