Este tutorial continuará a explorar alguns dos principais recursos do Spring Data MongoDB - a anotação@DBRef e eventos de ciclo de vida.
2. @DBRef
A estrutura de mapeamento não oferece suporte astoring parent-child relationse documentos incorporados em outros documentos. O que podemos fazer é - podemos armazená-los separadamente e usarDBRef para se referir aos documentos.
Quando o objeto é carregado do MongoDB, essas referências serão prontamente resolvidas 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;
EmailAddress se parece com:
@Document
public class EmailAddress {
@Id
private String id;
private String value;
// standard getters and setters
}
Observe que a estrutura de mapeamentodoesn’t handle cascading operations. Então - por exemplo - se acionarmossave em um dos pais, a criança não será salva automaticamente - precisaremos acionar explicitamente o salvamento na criança se quisermos salvá-la também.
É exatamente ondelife cycle events come in handy.
3. Eventos de Ciclo de Vida
Spring Data MongoDB publica alguns eventos de ciclo de vida muito úteis - comoonBeforeConvert, onBeforeSave, onAfterSave, onAfterLoadeonAfterConvert.
Para interceptar um dos eventos, precisamos registrar uma subclasse deAbstractMappingEventListenere substituir um dos métodos aqui. Quando o evento é despachado, nosso ouvinte será chamado e o objeto de domínio transmitido.
3.1. Salvar Cascade Básico
Vejamos o exemplo que tínhamos anteriormente - salvandouser comemailAddress. Agora podemos ouvir o eventoonBeforeConvert que será chamado antes de um objeto de domínio entrar no conversor:
public class UserCascadeSaveMongoEventListener extends AbstractMongoEventListener
Agora só precisamos registrar o ouvinte emMongoConfig:
@Bean
public UserCascadeSaveMongoEventListener userCascadingMongoEventListener() {
return new UserCascadeSaveMongoEventListener();
}
Ou como XML:
E nós temos a semântica em cascata pronta, embora apenas para o usuário.
3.2. Uma Implementação Genérica de Cascade
Vamos agora melhorar a solução anterior emmaking the cascade functionality generic. Vamos começar definindo uma anotação personalizada:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface CascadeSave {
//
}
Vamos agorawork on our custom listener para lidar com esses campos genericamente e não ter que lançar para qualquer entidade particular:
public class CascadeSaveMongoEventListener extends AbstractMongoEventListener {
@Autowired
private MongoOperations mongoOperations;
@Override
public void onBeforeConvert(BeforeConvertEvent 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 campos que tenham a anotaçãoDBRef e tambémCascadeSave. Depois de encontrarmos esses campos, salvamos a entidade filha.
Vejamos a classeFieldCallback que estamos usando para verificar se a criança tem uma anotação@Id:
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 que o campoemailAddress agora seja anotado corretamente:
Vamos agora dar uma olhada em um cenário - salvamos umUser comemailAddress, e a operação de salvamento se desdobra para esta entidade incorporada automaticamente:
User user = new User();
user.setName("Brendan");
EmailAddress emailAddress = new EmailAddress();
emailAddress.setValue("[email protected]");
user.setEmailAddress(emailAddress);
mongoTemplate.insert(user);
Neste artigo, ilustramos alguns recursos interessantes do Spring Data MongoDB - a anotação@DBRef, eventos de ciclo de vida e como podemos lidar com a cascata de forma inteligente.
A implementação de todos esses exemplos e trechos de códigocan be found over on GitHub - este é um projeto baseado em Maven, portanto, deve ser fácil de importar e executar como está.