アンラップ型
ラップされていないエンティティは、プロパティが親の MongoDB ドキュメントにフラット化される Java ドメインモデル内の値オブジェクトを設計するために使用されます。
アンラップされた型のマッピング
User.name
に @Unwrapped
のアノテーションが付けられている次のドメインモデルを考えてみましょう。@Unwrapped
アノテーションは、UserName
のすべてのプロパティを name
プロパティを所有する user
ドキュメントにフラット化する必要があることを示します。
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"
}
1 | name プロパティをロードするときに、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(" … ")
名の前に付加されます。複数のプロパティが同じフィールド名にレンダリングされる場合、値は互いに上書きされることに注意してください。
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"
}
1 | UserName のすべてのプロパティには、接頭辞 u_ が付きます。 |
2 | UserName のすべてのプロパティには、接頭辞 a_ が付きます。 |
まったく同じプロパティ上で @Field
アノテーションと @Unwrapped
を組み合わせることは意味をなさないため、エラーが発生します。アンラップされた型のプロパティに対して @Field
を使用することは、完全に有効なアプローチです。
@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"
}
1 | UserName のすべてのプロパティには、接頭辞 u- が付きます。 |
2 | 最終的なフィールド名は、@Unwrapped(prefix) と @Field(name) を連結した結果です。 |
ラップされていないオブジェクトに対するクエリ
提供された Criteria
がドメイン型と照合されるため、ラップされていないプロパティに対するクエリの定義は、型 レベルでもフィールドレベルでも可能です。実際のクエリをレンダリングするときに、プレフィックスと潜在的なカスタムフィールド名が考慮されます。以下のサンプルに示すように、ラップされていないオブジェクトのプロパティ名を使用して、含まれるすべてのフィールドと照合します。
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"
})
以下のスニペットに示すように、そのプロパティ名を使用して、ラップされていないオブジェクトの任意のフィールドを直接アドレス指定することもできます。
Query findByUserFirstName = query(where("name.firstname").is("Shuri"));
List<User> users = template.findAll(findByUserFirstName, User.class);
db.collection.find({
"firstname" : "Shuri"
})
ラップされていないフィールドで並べ替えます。
以下のサンプルに示すように、ラップされていないオブジェクトのフィールドは、プロパティパスを介して並べ替えに使用できます。
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 })
可能ではありますが、ラップされていないオブジェクト自体を並べ替え条件として使用すると、そのすべてのフィールドが予測できない順序で含まれるため、順序が不正確になる可能性があります。 |
アンラップされたオブジェクトへのフィールド射影
以下のサンプルに示すように、ラップされていないオブジェクトのフィールドは、全体として、または単一のフィールドを介して射影の対象にすることができます。
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 | アンラップされたオブジェクトのフィールド射影には、そのすべてのプロパティが含まれます。 |
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
抽象化により、ラップされていないオブジェクトのフィールドおよびオブジェクト全体に対するクエリを派生できます。
interface UserRepository extends CrudRepository<User, String> {
List<User> findByName(UserName username); (1)
List<User> findByNameFirstname(String firstname); (2)
}
1 | ラップされていないオブジェクトのすべてのフィールドと照合します。 |
2 | firstname と一致します。 |
リポジトリ |
ラップされていないオブジェクトの更新
ラップされていないオブジェクトは、ドメインモデルの一部である他のオブジェクトと同様に更新できます。マッピングレイヤーは、構造を周囲に平坦化する処理を行います。以下の例に示すように、ラップされていないオブジェクトの単一の属性だけでなく、値全体も更新できます。
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" }
},
{ ... }
)
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)
}
1 | users コレクション内の lastname に対して作成されたインデックス。 |
2 | @Indexed と @Unwrapped の併用が無効です |