MongoDB
 sql >> Datenbank >  >> NoSQL >> MongoDB

Benutzerdefinierte Kaskadierung in Spring Data MongoDB

1. Übersicht

In diesem Tutorial werden weiterhin einige der Kernfunktionen von Spring Data MongoDB untersucht – das @DBRef Anmerkungen und Lebenszyklusereignisse.

2. @DBRef

Das Mapping-Framework unterstützt das Speichern von Eltern-Kind-Beziehungen nicht und eingebettete Dokumente in anderen Dokumenten. Was wir jedoch tun können, ist – wir können sie separat speichern und ein DBRef 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 genauso aussieht, als wäre es eingebettet in unser Masterdokument gespeichert worden.

Schauen wir uns etwas Code an:

@DBRef
private EmailAddress emailAddress;

E-Mail-Adresse sieht so aus:

@Document
public class EmailAddress {
    @Id
    private String id;
    
    private String value;
    
    // standard getters and setters
}

Beachten Sie, dass das Mapping-Framework keine kaskadierenden Vorgänge verarbeitet . Also – zum Beispiel – wenn wir ein Speichern auslösen Bei einem übergeordneten Element wird das untergeordnete Element nicht automatisch gespeichert – wir müssen das Speichern explizit für das untergeordnete Element auslösen, wenn wir es ebenfalls speichern möchten.

Genau hier kommen Lebenszyklusereignisse ins Spiel .

3. Lebenszyklusereignisse

Spring Data MongoDB veröffentlicht einige sehr nützliche Lebenszyklusereignisse – wie onBeforeConvert, onBeforeSave, onAfterSave, onAfterLoad undonAfterConvert.

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

3.1. Einfacher Kaskadenspeicher

Schauen wir uns das Beispiel an, das wir zuvor hatten – Speichern des Benutzers mit der emailAddress . Wir können uns jetzt das onBeforeConvert anhören Ereignis, das aufgerufen wird, bevor ein Domänenobjekt in den Konverter gelangt:

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

Jetzt müssen wir nur noch den Listener in MongoConfig registrieren :

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

Oder als XML:

<bean class="org.baeldung.event.UserCascadeSaveMongoEventListener" />

Und wir haben alles für die kaskadierende Semantik getan – wenn auch nur für den Benutzer.

3.2. Eine generische Kaskadenimplementierung

Lassen Sie uns nun die vorherige Lösung verbessern, indem wir die Kaskadenfunktion generisch machen. Beginnen wir mit der Definition einer benutzerdefinierten Anmerkung:

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

Lassen Sie uns jetzt an unserem benutzerdefinierten Listener arbeiten um diese Felder generisch zu handhaben und nicht in eine bestimmte Entität umwandeln zu müssen:

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

Wir verwenden also das Reflection-Dienstprogramm aus Spring und führen unseren Callback für alle Felder aus, die unsere Kriterien erfüllen:

@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 können, suchen wir nach Feldern, die sowohl den DBRef aufweisen Anmerkung sowie CascadeSave . Sobald wir diese Felder gefunden haben, speichern wir die untergeordnete Entität.

Schauen wir uns den FieldCallback an Klasse, mit der wir prüfen, ob das Kind eine @Id hat Anmerkung:

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 zusammen funktioniert, benötigen wir natürlich emailAddress Feld nun korrekt annotiert:

@DBRef
@CascadeSave
private EmailAddress emailAddress;

3.3. Der Kaskadentest

Schauen wir uns nun ein Szenario an – wir speichern einen Benutzer mit E-Mail-Adresse , und der Speichervorgang wird automatisch zu dieser eingebetteten Entität kaskadiert:

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

Sehen wir uns unsere Datenbank an:

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