SpringDataMongoDBでのカスタムカスケード
1. 概要
このチュートリアルでは、Spring Data MongoDBのコア機能のいくつか( @DBRef アノテーションとライフサイクルイベント)について引き続き説明します。
2. @DBRef
マッピングフレームワークは、親子関係の保存および他のドキュメント内の埋め込みドキュメントをサポートしていません。 ただし、できることは、それらを個別に保存し、DBRefを使用してドキュメントを参照できることです。
オブジェクトがMongoDBから読み込まれると、それらの参照は熱心に解決され、マスタードキュメント内に埋め込まれて保存されているかのように見えるマップされたオブジェクトが返されます。
いくつかのコードを見てみましょう:
@DBRef
private EmailAddress emailAddress;
EmailAddressは次のようになります。
@Document
public class EmailAddress {
@Id
private String id;
private String value;
// standard getters and setters
}
マッピングフレームワークはカスケード操作を処理しないことに注意してください。 したがって、たとえば、親で save をトリガーした場合、子は自動的に保存されません。子も保存する場合は、子で明示的に保存をトリガーする必要があります。
これはまさにライフサイクルイベントが役立つ場所です。
3. ライフサイクルイベント
Spring Data MongoDBは、 onBeforeConvert、onBeforeSave、onAfterSave、onAfterLoad 、onAfterConvertなどの非常に便利なライフサイクルイベントを公開しています。
イベントの1つをインターセプトするには、 AbstractMappingEventListener のサブクラスを登録し、ここでメソッドの1つをオーバーライドする必要があります。 イベントがディスパッチされると、リスナーが呼び出され、ドメインオブジェクトが渡されます。
3.1. 基本的なカスケード保存
以前の例を見てみましょう– userをemailAddressで保存します。 これで、ドメインオブジェクトがコンバーターに入る前に呼び出されるonBeforeConvertイベントをリッスンできます。
public class UserCascadeSaveMongoEventListener extends AbstractMongoEventListener<Object> {
@Autowired
private MongoOperations mongoOperations;
@Override
public void onBeforeConvert(BeforeConvertEvent<Object> event) {
Object source = event.getSource();
if ((source instanceof User) && (((User) source).getEmailAddress() != null)) {
mongoOperations.save(((User) source).getEmailAddress());
}
}
}
次に、リスナーをMongoConfigに登録する必要があります。
@Bean
public UserCascadeSaveMongoEventListener userCascadingMongoEventListener() {
return new UserCascadeSaveMongoEventListener();
}
またはXMLとして:
<bean class="org.baeldung.event.UserCascadeSaveMongoEventListener" />
そして、ユーザーのみを対象としていますが、カスケードセマンティクスはすべて完了しています。
3.2. 一般的なカスケードの実装
以前のソリューションを次のように改善しましょう
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface CascadeSave {
//
}
次に、カスタムリスナーでこれらのフィールドを一般的に処理し、特定のエンティティにキャストする必要がないようにします。
public class CascadeSaveMongoEventListener extends AbstractMongoEventListener<Object> {
@Autowired
private MongoOperations mongoOperations;
@Override
public void onBeforeConvert(BeforeConvertEvent<Object> event) {
Object source = event.getSource();
ReflectionUtils.doWithFields(source.getClass(),
new CascadeCallback(source, mongoOperations));
}
}
したがって、Springのリフレクションユーティリティを使用しており、基準を満たすすべてのフィールドでコールバックを実行しています。
@Override
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
ReflectionUtils.makeAccessible(field);
if (field.isAnnotationPresent(DBRef.class) &&
field.isAnnotationPresent(CascadeSave.class)) {
Object fieldValue = field.get(getSource());
if (fieldValue != null) {
FieldCallback callback = new FieldCallback();
ReflectionUtils.doWithFields(fieldValue.getClass(), callback);
getMongoOperations().save(fieldValue);
}
}
}
ご覧のとおり、DBRefアノテーションとCascadeSaveの両方を持つフィールドを探しています。 これらのフィールドが見つかったら、子エンティティを保存します。
子に@Idアノテーションがあるかどうかを確認するために使用しているFieldCallbackクラスを見てみましょう。
public class FieldCallback implements ReflectionUtils.FieldCallback {
private boolean idFound;
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
ReflectionUtils.makeAccessible(field);
if (field.isAnnotationPresent(Id.class)) {
idFound = true;
}
}
public boolean isIdFound() {
return idFound;
}
}
最後に、すべてを連携させるには、もちろん、emailAddressフィールドに正しく注釈を付ける必要があります。
@DBRef
@CascadeSave
private EmailAddress emailAddress;
3.3. カスケードテスト
シナリオを見てみましょう。UserをemailAddressで保存すると、保存操作がこの埋め込みエンティティに自動的にカスケードされます。
User user = new User();
user.setName("Brendan");
EmailAddress emailAddress = new EmailAddress();
emailAddress.setValue("[email protected]");
user.setEmailAddress(emailAddress);
mongoTemplate.insert(user);
私たちのデータベースをチェックしましょう:
{
"_id" : ObjectId("55cee9cc0badb9271768c8b9"),
"name" : "Brendan",
"age" : null,
"email" : {
"value" : "[email protected]"
}
}
4. 結論
この記事では、Spring Data MongoDBのいくつかの優れた機能( @DBRef アノテーション、ライフサイクルイベント、およびカスケードをインテリジェントに処理する方法)について説明しました。
これらすべての例とコードスニペットの実装は、GitHub にあります。これはMavenベースのプロジェクトであるため、そのままインポートして実行するのは簡単です。