sql >> Database >  >> NoSQL >> MongoDB

Aangepaste cascadering in Spring Data MongoDB

1. Overzicht

In deze zelfstudie worden enkele van de kernfuncties van Spring Data MongoDB verder onderzocht:de @DBref annotatie en levenscyclusgebeurtenissen.

2. @DBref

Het mapping-framework ondersteunt geen het opslaan van ouder-kindrelaties en ingesloten documenten in andere documenten. Wat we wel kunnen doen is – we kunnen ze apart opslaan en een DBref . gebruiken om naar de documenten te verwijzen.

Wanneer het object vanuit MongoDB is geladen, worden die verwijzingen gretig opgelost en krijgen we een toegewezen object terug dat er hetzelfde uitziet alsof het in ons hoofddocument was opgeslagen.

Laten we eens kijken naar wat code:

@DBRef
private EmailAddress emailAddress;

E-mailadres ziet eruit als:

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

Merk op dat het mapping framework geen trapsgewijze bewerkingen verwerkt . Dus – bijvoorbeeld – als we een opslaan . activeren op een ouder wordt het kind niet automatisch opgeslagen - we moeten het opslaan van het kind expliciet activeren als we het ook willen opslaan.

Dit is precies waar levenscyclusgebeurtenissen van pas komen .

3. Levenscyclusgebeurtenissen

Spring Data MongoDB publiceert enkele zeer nuttige levenscyclusgebeurtenissen - zoals onBeforeConvert, onBeforeSave, onAfterSave, onAfterLoad en onAfterConvert.

Om een ​​van de gebeurtenissen te onderscheppen, moeten we een subklasse van AbstractMappingEventListener registreren en overschrijf een van de methoden hier. Wanneer de gebeurtenis wordt verzonden, wordt onze luisteraar gebeld en het domeinobject doorgegeven.

3.1. Basis Cascade Opslaan

Laten we eens kijken naar het voorbeeld dat we eerder hadden – het opslaan van de gebruiker met het e-mailadres . We kunnen nu luisteren naar de onBeforeConvert gebeurtenis die wordt aangeroepen voordat een domeinobject in de converter gaat:

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

Nu hoeven we alleen de luisteraar te registreren in MongoConfig :

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

Of als XML:

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

En we hebben de trapsgewijze semantiek allemaal gedaan – zij het alleen voor de gebruiker.

3.2. Een algemene cascade-implementatie

Laten we nu de vorige oplossing verbeteren door de cascadefunctionaliteit generiek te maken. Laten we beginnen met het definiëren van een aangepaste annotatie:

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

Laten we nu aan onze aangepaste luisteraar werken om deze velden generiek te behandelen en niet naar een bepaalde entiteit te hoeven casten:

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

We gebruiken dus het reflectiehulpprogramma van Spring, en we voeren onze callback uit op alle velden die aan onze criteria voldoen:

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

Zoals u kunt zien, zijn we op zoek naar velden die zowel de DBref . hebben annotatie en CascadeSave . Zodra we deze velden hebben gevonden, slaan we de onderliggende entiteit op.

Laten we eens kijken naar de FieldCallback klasse die we gebruiken om te controleren of het kind een @Id . heeft annotatie:

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

Tot slot, om alles samen te laten werken, moeten we natuurlijk emailAddress veld dat nu correct moet worden geannoteerd:

@DBRef
@CascadeSave
private EmailAddress emailAddress;

3.3. De cascadetest

Laten we nu een scenario bekijken - we slaan een Gebruiker op met e-mailadres , en de opslagbewerking wordt automatisch naar deze ingesloten entiteit gecascadeerd:

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

Laten we eens kijken in onze database:

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

  1. Mongoose belooft documentatie zegt dat zoekopdrachten geen beloften zijn?

  2. Hoe GridFS te gebruiken om afbeeldingen op te slaan met Node.js en Mongoose

  3. Tot welk niveau vergrendelt MongoDB schrijven? (of:wat wordt bedoeld met per aansluiting)

  4. MongooseError - Bewerking `users.findOne()` buffering time-out na 10000ms