Benutzerdefinierte Kaskadierung in Spring Data MongoDB

Benutzerdefinierte Kaskadierung in Spring Data MongoDB

1. Überblick

In diesem Tutorial werden weiterhin einige der Hauptfunktionen von Spring Data MongoDB untersucht - die Annotations- und Lebenszyklusereignisse von@DBRef.

2. @DBRef

Das Mapping-Framework unterstützt keinestoring parent-child relations und eingebetteten Dokumente in anderen Dokumenten. Was wir jedoch tun können, ist - wir können sie separat speichern und einDBRef verwenden, um auf die Dokumente zu verweisen.

Wenn das Objekt aus MongoDB geladen wird, werden diese Verweise eifrig aufgelöst, und wir erhalten ein zugeordnetes Objekt zurück, das so aussieht, als wäre es eingebettet in unser Masterdokument gespeichert worden.

Schauen wir uns einen Code an:

@DBRef
private EmailAddress emailAddress;

EmailAddress sieht aus wie:

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

    private String value;

    // standard getters and setters
}

Beachten Sie, dass das Mapping-Frameworkdoesn’t handle cascading operations. Wenn wir beispielsweise einsave für ein Elternteil auslösen, wird das Kind nicht automatisch gespeichert. Wir müssen das Speichern für das Kind explizit auslösen, wenn wir es auch speichern möchten.

Genau hier setztlife cycle events come in handy an.

3. Lebenszyklusereignisse

Spring Data MongoDB veröffentlicht einige sehr nützliche Lebenszyklusereignisse - wieonBeforeConvert, onBeforeSave, onAfterSave, onAfterLoad undonAfterConvert.

Um eines der Ereignisse abzufangen, müssen wir eine Unterklasse vonAbstractMappingEventListener registrieren und eine der hier beschriebenen Methoden überschreiben. Wenn das Ereignis ausgelöst wird, wird unser Listener aufgerufen und das Domänenobjekt übergeben.

3.1. Grundlegende Kaskadenspeicherung

Schauen wir uns das Beispiel an, das wir zuvor hatten - Speichern deruser mit denemailAddress. Wir können jetzt das EreignisonBeforeConvertabhören, das aufgerufen wird, bevor ein Domänenobjekt in den Konverter gelangt:

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());
        }
    }
}


Jetzt müssen wir nur noch den Listener inMongoConfig registrieren:

@Bean
public UserCascadeSaveMongoEventListener userCascadingMongoEventListener() {
    return new UserCascadeSaveMongoEventListener();
}

Oder als XML:

Und wir haben alle Semantik-Kaskaden erstellt - allerdings nur für den Benutzer.

3.2. Eine generische Kaskadenimplementierung

Verbessern wir jetzt die vorherige Lösung ummaking the cascade functionality generic.. Beginnen wir mit der Definition einer benutzerdefinierten Anmerkung:

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

Lassen Sie uns nunwork on our custom listener, um diese Felder generisch zu behandeln und nicht in eine bestimmte Entität umwandeln zu müssen:

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));
    }
}


Daher verwenden wir das Reflection-Dienstprogramm ab Spring und führen unseren Rückruf für alle Felder aus, die unseren Kriterien entsprechen:

@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);
        }
    }
}

Wie Sie sehen, suchen wir nach Feldern, die sowohl die AnnotationDBRefals auchCascadeSaveenthalten. Sobald wir diese Felder gefunden haben, speichern wir die untergeordnete Entität.

Schauen wir uns die KlasseFieldCallbackan, mit der wir überprüfen, ob das Kind eine Anmerkung von@Idhat:

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;
    }
}

Damit alles zusammenarbeitet, müssen wir natürlich das FeldemailAddressverwenden, um jetzt korrekt kommentiert zu werden:

@DBRef
@CascadeSave
private EmailAddress emailAddress;

3.3. Der Kaskadentest

Schauen wir uns nun ein Szenario an: Wir speichernUser mitemailAddress, und die Speicheroperation wird automatisch auf diese eingebettete Entität übertragen:

User user = new User();
user.setName("Brendan");
EmailAddress emailAddress = new EmailAddress();
emailAddress.setValue("[email protected]");
user.setEmailAddress(emailAddress);
mongoTemplate.insert(user);

Überprüfen wir unsere Datenbank:

{
    "_id" : ObjectId("55cee9cc0badb9271768c8b9"),
    "name" : "Brendan",
    "age" : null,
    "email" : {
        "value" : "[email protected]"
    }
}

4. Fazit

In diesem Artikel haben wir einige coole Funktionen von Spring Data MongoDB veranschaulicht - die Annotation von@DBRef, Lebenszyklusereignisse und wie wir intelligent mit Kaskadierung umgehen können.

Die Implementierung all dieser Beispiele und Codefragmentecan be found over on GitHub - dies ist ein Maven-basiertes Projekt, daher sollte es einfach zu importieren und auszuführen sein, wie es ist.