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

Cascata personalizzata in Spring Data MongoDB

1. Panoramica

Questo tutorial continuerà ad esplorare alcune delle funzionalità principali di Spring Data MongoDB:il @DBRef annotazione ed eventi del ciclo di vita.

2. @DBRef

Il framework di mappatura non supporta la archiviazione delle relazioni genitore-figlio e documenti incorporati in altri documenti. Quello che possiamo fare però è:possiamo archiviarli separatamente e utilizzare un DBRef per fare riferimento ai documenti.

Quando l'oggetto viene caricato da MongoDB, quei riferimenti verranno risolti con entusiasmo e restituiremo un oggetto mappato che ha lo stesso aspetto di se fosse stato memorizzato incorporato nel nostro documento master.

Diamo un'occhiata al codice:

@DBRef
private EmailAddress emailAddress;

Indirizzo email assomiglia a:

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

Tieni presente che il framework di mappatura non gestisce le operazioni a cascata . Quindi, ad esempio, se attiviamo un salvataggio su un genitore, il figlio non verrà salvato automaticamente:dovremo attivare esplicitamente il salvataggio sul figlio se vogliamo salvarlo anche noi.

È proprio qui che gli eventi del ciclo di vita tornano utili .

3. Eventi del ciclo di vita

Spring Data MongoDB pubblica alcuni utili eventi del ciclo di vita, come onBeforeConvert, onBeforeSave, onAfterSave, onAfterLoad e su AfterConvert.

Per intercettare uno degli eventi, dobbiamo registrare una sottoclasse di AbstractMappingEventListener e sovrascrivi uno dei metodi qui. Quando l'evento viene inviato, il nostro listener verrà chiamato e l'oggetto di dominio verrà passato.

3.1. Salvataggio a cascata di base

Diamo un'occhiata all'esempio che abbiamo avuto in precedenza:salvare l'utente con l'indirizzo email . Ora possiamo ascoltare onBeforeConvert evento che verrà chiamato prima che un oggetto dominio entri nel convertitore:

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

Ora dobbiamo solo registrare l'ascoltatore in MongoConfig :

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

O come XML:

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

E abbiamo fatto tutto la semantica a cascata, anche se solo per l'utente.

3.2. Un'implementazione a cascata generica

Miglioriamo ora la soluzione precedente rendendo generica la funzionalità a cascata. Iniziamo definendo un'annotazione personalizzata:

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

Ora lavoriamo sul nostro listener personalizzato per gestire questi campi in modo generico e non dover eseguire il cast a un'entità particolare:

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

Quindi utilizziamo l'utilità di riflessione a partire dalla primavera ed eseguiamo il nostro callback su tutti i campi che soddisfano i nostri criteri:

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

Come puoi vedere, stiamo cercando campi che abbiano entrambi DBRef annotazione e CascadeSave . Una volta trovati questi campi, salviamo l'entità figlio.

Diamo un'occhiata al FieldCallback classe che stiamo usando per verificare se il bambino ha un @Id annotazione:

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

Infine, per far funzionare tutto insieme, ovviamente, dobbiamo emailAddress campo da annotare correttamente:

@DBRef
@CascadeSave
private EmailAddress emailAddress;

3.3. Il test a cascata

Diamo ora un'occhiata a uno scenario:salviamo un Utente con indirizzo email e l'operazione di salvataggio si sovrappone automaticamente a questa entità incorporata:

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

Controlliamo il nostro database:

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