Cascading personnalisé dans Spring Data MongoDB

Cascade personnalisée dans Spring Data MongoDB

1. Vue d'ensemble

Ce didacticiel continuera d'explorer certaines des fonctionnalités de base de Spring Data MongoDB - l'annotation@DBRef et les événements du cycle de vie.

2. @DBRef

La structure de mappage ne prend pas en charge lesstoring parent-child relations et les documents incorporés dans d’autres documents. Ce que nous pouvons faire, c'est - nous pouvons les stocker séparément et utiliser unDBRef pour faire référence aux documents.

Lorsque l'objet est chargé à partir de MongoDB, ces références seront résolues avec empressement, et nous récupérerons un objet mappé qui a le même aspect que s'il avait été stocké incorporé dans notre document maître.

Regardons un peu de code:

@DBRef
private EmailAddress emailAddress;

EmailAddress ressemble à:

@Document
public class EmailAddress {
    @Id
    private String id;

    private String value;

    // standard getters and setters
}

Notez que le framework de mappagedoesn’t handle cascading operations. Ainsi, par exemple, si nous déclenchons unsave sur un parent, l'enfant ne sera pas enregistré automatiquement - nous devrons déclencher explicitement la sauvegarde sur l'enfant si nous voulons l'enregistrer également.

C'est précisément là oùlife cycle events come in handy.

3. Événements du cycle de vie

Spring Data MongoDB publie des événements de cycle de vie très utiles - tels queonBeforeConvert, onBeforeSave, onAfterSave, onAfterLoad etonAfterConvert.

Pour intercepter l'un des événements, nous devons enregistrer une sous-classe deAbstractMappingEventListener et remplacer l'une des méthodes ici. Lorsque l'événement est distribué, notre auditeur sera appelé et l'objet domain sera transmis.

3.1. Sauvegarde en cascade de base

Regardons l'exemple que nous avons eu précédemment: enregistrer lesuser avec lesemailAddress. Nous pouvons maintenant écouter l'événementonBeforeConvert qui sera appelé avant qu'un objet de domaine n'entre dans le convertisseur:

public class UserCascadeSaveMongoEventListener extends AbstractMongoEventListener {
    @Autowired
    private MongoOperations mongoOperations;

    @Override
    public void onBeforeConvert(BeforeConvertEvent event) {
        Object source = event.getSource();
        if ((source instanceof User) && (((User) source).getEmailAddress() != null)) {
            mongoOperations.save(((User) source).getEmailAddress());
        }
    }
}


Il ne nous reste plus qu'à enregistrer l'auditeur dansMongoConfig:

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

Ou en XML:

Et nous avons fait de la sémantique en cascade, mais uniquement pour l'utilisateur.

3.2. Une implémentation générique en cascade

Améliorons maintenant la solution précédente demaking the cascade functionality generic. Commençons par définir une annotation personnalisée:

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

Nous allons maintenantwork on our custom listener pour gérer ces champs de manière générique et ne pas avoir à convertir en une entité particulière:

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


Nous utilisons donc l'utilitaire de réflexion de Spring, et nous exécutons notre rappel sur tous les champs qui répondent à nos critères:

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

Comme vous pouvez le voir, nous recherchons des champs contenant à la fois l'annotationDBRef etCascadeSave. Une fois que nous avons trouvé ces champs, nous sauvons l'entité enfant.

Examinons la classeFieldCallback que nous utilisons pour vérifier si l'enfant a une annotation@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;
    }
}

Enfin, pour que tout fonctionne ensemble, nous avons bien sûr besoin du champemailAddress pour être maintenant correctement annoté:

@DBRef
@CascadeSave
private EmailAddress emailAddress;

3.3. Le test en cascade

Examinons maintenant un scénario - nous enregistrons unUser avecemailAddress, et l’opération de sauvegarde se répercute automatiquement sur cette entité intégrée:

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

Vérifions notre base de données:

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

4. Conclusion

Dans cet article, nous avons illustré quelques fonctionnalités intéressantes de Spring Data MongoDB - l'annotation@DBRef, les événements du cycle de vie et comment nous pouvons gérer intelligemment la cascade.

L'implémentation de tous ces exemples et extraits de codecan be found over on GitHub - il s'agit d'un projet basé sur Maven, il devrait donc être facile à importer et à exécuter tel quel.