sql >> Database teknologi >  >> NoSQL >> MongoDB

Custom Cascading i Spring Data MongoDB

1. Oversigt

Denne vejledning vil fortsætte med at udforske nogle af kernefunktionerne i Spring Data MongoDB – @DBRef annotering og livscyklusbegivenheder.

2. @DBRef

Kortlægningsrammen understøtter ikke lagring af forældre-barn-relationer og indlejrede dokumenter i andre dokumenter. Hvad vi dog kan gøre er – vi kan gemme dem separat og bruge en DBRef at henvise til dokumenterne.

Når objektet er indlæst fra MongoDB, bliver disse referencer ivrigt løst, og vi får et kortlagt objekt tilbage, der ser ud som om det var blevet gemt indlejret i vores masterdokument.

Lad os se på noget kode:

@DBRef
private EmailAddress emailAddress;

E-mailadresse ser ud som:

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

Bemærk, at kortlægningsrammen ikke håndterer overlappende operationer . Så – for eksempel – hvis vi udløser en gem på en forælder bliver barnet ikke gemt automatisk – vi skal eksplicit udløse lagringen på barnet, hvis vi også vil gemme det.

Det er netop her livscyklusbegivenheder kommer til nytte .

3. Livscyklusbegivenheder

Spring Data MongoDB udgiver nogle meget nyttige livscyklusbegivenheder – såsom onBeforeConvert, onBeforeSave, onAfterSave, onAfterLoad ogonAfterConvert.

For at opsnappe en af ​​begivenhederne skal vi registrere en underklasse af AbstractMappingEventListener og tilsidesæt en af ​​metoderne her. Når begivenheden afsendes, vil vores lytter blive kaldt, og domæneobjektet sendes ind.

3.1. Grundlæggende Cascade Save

Lad os se på det eksempel, vi havde tidligere – at gemme brugeren med emailAddress . Vi kan nu lytte til onBeforeConvert hændelse, som vil blive kaldt før et domæneobjekt går ind i konverteren:

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 mangler vi bare at registrere lytteren i MongoConfig :

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

Eller som XML:

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

Og vi har alt sammen lavet cascading semantik – dog kun for brugeren.

3.2. En generisk kaskadeimplementering

Lad os nu forbedre den tidligere løsning ved at gøre kaskadefunktionaliteten generisk. Lad os starte med at definere en brugerdefineret annotation:

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

Lad os nu arbejde på vores tilpassede lytter at håndtere disse felter generisk og ikke at skulle caste til nogen bestemt enhed:

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

Så vi bruger refleksionsværktøjet fra Spring, og vi kører vores tilbagekald på alle felter, der opfylder vores kriterier:

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

Som du kan se, leder vi efter felter, der har både DBRef annotation samt CascadeSave . Når vi har fundet disse felter, gemmer vi den underordnede enhed.

Lad os se på FieldCallback klasse, som vi bruger til at kontrollere, om barnet har et @Id anmærkning:

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

Til sidst, for at få det hele til at hænge sammen, skal vi selvfølgelig have emailAddress felt for nu at blive korrekt kommenteret:

@DBRef
@CascadeSave
private EmailAddress emailAddress;

3.3. Cascade Test

Lad os nu se på et scenario – vi gemmer en Bruger med e-mailadresse , og lagringsoperationen kaskades automatisk til denne indlejrede enhed:

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

Lad os tjekke vores database:

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

  1. Hvordan implementerer man server push i Flask framework?

  2. Hvad er det næste for Impala efter udgivelse 1.1

  3. Introduktion til Redis

  4. Hvordan kan jeg forespørge mongodb ved hjælp af mongoid/skinner uden timeout?