Spring Data MongoDBでのカスタムカスケード

Spring Data MongoDBのカスタムカスケード

1. 概要

このチュートリアルでは、Spring Data MongoDBのコア機能のいくつか(@DBRefアノテーションとライフサイクルイベント)について引き続き説明します。

2. @DBRef

マッピングフレームワークは、storing parent-child relationsおよび他のドキュメント内の埋め込みドキュメントをサポートしていません。 ただし、できることは、それらを個別に保存し、DBRefを使用してドキュメントを参照することです。

オブジェクトがMongoDBから読み込まれると、それらの参照は熱心に解決され、マスタードキュメント内に埋め込まれて保存されているかのように見えるマップされたオブジェクトが返されます。

いくつかのコードを見てみましょう:

@DBRef
private EmailAddress emailAddress;

EmailAddressは次のようになります。

@Document
public class EmailAddress {
    @Id
    private String id;

    private String value;

    // standard getters and setters
}

マッピングフレームワークdoesn’t handle cascading operationsに注意してください。 したがって、たとえば、親でsaveをトリガーした場合、子は自動的に保存されません。子も保存する場合は、子で明示的に保存をトリガーする必要があります。

これはまさにlife cycle events come in handyの場所です。

3. ライフサイクルイベント

Spring Data MongoDBは、onBeforeConvert, onBeforeSave, onAfterSave, onAfterLoadonAfterConvert.などの非常に便利なライフサイクルイベントを公開しています。

イベントの1つをインターセプトするには、AbstractMappingEventListenerのサブクラスを登録し、ここでメソッドの1つをオーバーライドする必要があります。 イベントがディスパッチされると、リスナーが呼び出され、ドメインオブジェクトが渡されます。

3.1. 基本的なカスケード保存

以前の例を見てみましょう–useremailAddressと一緒に保存します。 これで、ドメインオブジェクトがコンバーターに入る前に呼び出されるonBeforeConvertイベントをリッスンできます。

public class UserCascadeSaveMongoEventListener extends AbstractMongoEventListener {
    @Autowired
    private MongoOperations mongoOperations;

    @Override
    public void onBeforeConvert(BeforeConvertEvent 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として:

そして、カスケードセマンティクスをすべて完了しました。ただし、ユーザーのみが対象です。

3.2. 一般的なカスケード実装

前のソリューションをmaking the cascade functionality generic.改善してみましょう。カスタムアノテーションを定義することから始めましょう。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface CascadeSave {
    //
}

これらのフィールドを一般的に処理し、特定のエンティティにキャストする必要がないように、work on our custom listenerを使用してみましょう。

public class CascadeSaveMongoEventListener extends AbstractMongoEventListener {

    @Autowired
    private MongoOperations mongoOperations;

    @Override
    public void onBeforeConvert(BeforeConvertEvent 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. カスケードテスト

シナリオを見てみましょう。UseremailAddressで保存すると、保存操作がこの埋め込みエンティティに自動的にカスケードされます。

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アノテーション、ライフサイクルイベント、およびカスケードをインテリジェントに処理する方法)について説明しました。

これらすべての例とコードスニペットcan be found over on GitHubの実装–これはMavenベースのプロジェクトであるため、そのままインポートして実行するのは簡単です。