sql >> Databasteknik >  >> NoSQL >> MongoDB

Custom Cascading in Spring Data MongoDB

1. Översikt

Denna handledning kommer att fortsätta att utforska några av kärnfunktionerna i Spring Data MongoDB – @DBRef anteckningar och livscykelhändelser.

2. @DBRef

Mappningsramverket stöder inte lagring av relationer mellan föräldrar och barn och inbäddade dokument i andra dokument. Vad vi dock kan göra är – vi kan lagra dem separat och använda en DBRef att hänvisa till dokumenten.

När objektet laddas från MongoDB kommer dessa referenser att lösas ivrigt, och vi kommer att få tillbaka ett mappat objekt som ser likadant ut som om det hade lagrats inbäddat i vårt huvuddokument.

Låt oss titta på lite kod:

@DBRef
private EmailAddress emailAddress;

E-postadress ser ut som:

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

Observera att mappningsramverket inte hanterar överlappande operationer . Så – till exempel – om vi utlöser en spara på en förälder kommer barnet inte att sparas automatiskt – vi måste explicit utlösa sparandet på barnet om vi vill spara det också.

Det är just här livscykelhändelser kommer väl till pass .

3. Livscykelhändelser

Spring Data MongoDB publicerar några mycket användbara livscykelhändelser – som onBeforeConvert, onBeforeSave, onAfterSave, onAfterLoad ochonAfterConvert.

För att fånga upp en av händelserna måste vi registrera en underklass av AbstractMappingEventListener och åsidosätt en av metoderna här. När händelsen skickas kommer vår lyssnare att anropas och domänobjektet skickas in.

3.1. Basic Cascade Save

Låt oss titta på exemplet vi hade tidigare – att spara användaren med e-postadressen . Vi kan nu lyssna på onBeforeConvert händelse som kommer att anropas innan ett domänobjekt går in i omvandlaren:

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 behöver vi bara registrera lyssnaren i MongoConfig :

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

Eller som XML:

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

Och vi har allt gjort genomgående semantik – om än bara för användaren.

3.2. En generisk kaskadimplementering

Låt oss nu förbättra den tidigare lösningen genom att göra kaskadfunktionen generisk. Låt oss börja med att definiera en anpassad anteckning:

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

Låt oss nu arbeta med vår anpassade lyssnare för att hantera dessa fält generiskt och inte behöva casta till någon speciell enhet:

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 använder reflektionsverktyget från Spring, och vi kör vår callback på alla fält som uppfyller våra 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 letar vi efter fält som har både DBRef anteckning samt CascadeSave . När vi har hittat dessa fält sparar vi den underordnade enheten.

Låt oss titta på FieldCallback klass som vi använder för att kontrollera om barnet har ett @Id anteckning:

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

Slutligen, för att få det hela att fungera tillsammans måste vi naturligtvis e-postadress fältet ska nu vara korrekt kommenterat:

@DBRef
@CascadeSave
private EmailAddress emailAddress;

3.3. Kaskadtestet

Låt oss nu ta en titt på ett scenario – vi sparar en Användare med e-postadress , och spara operationen kaskader till denna inbäddade enhet automatiskt:

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

Låt oss kolla vår databas:

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

  1. Hur utför man Persistence Store i Redis?

  2. Få antalet öppna anslutningar i mongoDB med java

  3. 3 sätt att dölja ett index från frågeplanen i MongoDB

  4. Hur tar man bort ett "dokument" med "ID" med den officiella C#-drivrutinen för MongoDB?