MongoDB
 sql >> Base de Dados >  >> NoSQL >> MongoDB

Cascata personalizada no Spring Data MongoDB

1. Visão geral


Este tutorial continuará a explorar alguns dos principais recursos do Spring Data MongoDB – o @DBRef anotação e eventos de ciclo de vida.

2. @DBRef


A estrutura de mapeamento não suporta armazenar relações pai-filho e documentos embutidos em outros documentos. O que podemos fazer é - podemos armazená-los separadamente e usar um DBRef para consultar os documentos.

Quando o objeto for carregado do MongoDB, essas referências serão resolvidas rapidamente e obteremos de volta um objeto mapeado que parece o mesmo que se tivesse sido armazenado incorporado em nosso documento mestre.

Vejamos alguns códigos:
@DBRef
private EmailAddress emailAddress;

Endereço de e-mail parece:
@Document
public class EmailAddress {
    @Id
    private String id;
    
    private String value;
    
    // standard getters and setters
}

Observe que a estrutura de mapeamento não lida com operações em cascata . Então – por exemplo – se acionarmos um salvar em um pai, o filho não será salvo automaticamente – precisaremos acionar explicitamente o salvamento no filho se quisermos salvá-lo também.

É exatamente aqui que os eventos do ciclo de vida são úteis .

3. Eventos de ciclo de vida


O Spring Data MongoDB publica alguns eventos de ciclo de vida muito úteis - como onBeforeConvert, onBeforeSave, onAfterSave, onAfterLoad e onAfterConvert.

Para interceptar um dos eventos, precisamos registrar uma subclasse de AbstractMappingEventListener e substituir um dos métodos aqui. Quando o evento for despachado, nosso ouvinte será chamado e o objeto de domínio será passado.

3.1. Salvamento básico em cascata


Vejamos o exemplo que tivemos anteriormente – salvando o usuário com o endereço de e-mail . Agora podemos ouvir o onBeforeConvert evento que será chamado antes de um objeto de domínio entrar no conversor:
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());
        }
    }
}

Agora só precisamos registrar o listener no MongoConfig :
@Bean
public UserCascadeSaveMongoEventListener userCascadingMongoEventListener() {
    return new UserCascadeSaveMongoEventListener();
}

Ou como XML:
<bean class="org.baeldung.event.UserCascadeSaveMongoEventListener" />

E temos toda a semântica em cascata – embora apenas para o usuário.

3.2. Uma implementação genérica em cascata


Vamos agora melhorar a solução anterior tornando a funcionalidade de cascata genérica. Vamos começar definindo uma anotação personalizada:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface CascadeSave {
    //
}

Vamos agora trabalhar em nosso ouvinte personalizado para lidar com esses campos genericamente e não precisar converter para nenhuma entidade específica:
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));
    }
}

Portanto, estamos usando o utilitário de reflexão do Spring e executando nosso retorno de chamada em todos os campos que atendem aos nossos critérios:
@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);
        }
    }
}

Como você pode ver, estamos procurando por campos que tenham o DBRef anotação, bem como CascadeSave . Assim que encontrarmos esses campos, salvamos a entidade filha.

Vejamos o FieldCallback classe que estamos usando para verificar se o filho tem um @Id anotação:
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;
    }
}

Por fim, para que tudo funcione em conjunto, é claro que precisamos emailAddress campo para agora ser anotado corretamente:
@DBRef
@CascadeSave
private EmailAddress emailAddress;

3.3. O teste em cascata


Vamos agora dar uma olhada em um cenário – salvamos um Usuário com endereço de e-mail , e a operação de salvamento se propaga automaticamente para esta entidade incorporada:
User user = new User();
user.setName("Brendan");
EmailAddress emailAddress = new EmailAddress();
emailAddress.setValue("[email protected]");
user.setEmailAddress(emailAddress);
mongoTemplate.insert(user);

Vamos verificar nosso banco de dados:
{
    "_id" : ObjectId("55cee9cc0badb9271768c8b9"),
    "name" : "Brendan",
    "age" : null,
    "email" : {
        "value" : "[email protected]"
    }
}