Пользовательское каскадирование в Spring Data MongoDB

Пользовательское каскадирование весной данных MongoDB

1. обзор

В этом руководстве мы продолжим изучение некоторых основных функций Spring Data MongoDB - аннотации@DBRef и событий жизненного цикла.

2. @DBRefс

Платформа сопоставления не поддерживаетstoring parent-child relations и встроенные документы в другие документы. Однако мы можем хранить их отдельно и использоватьDBRef для ссылки на документы.

Когда объект загружается из MongoDB, эти ссылки будут быстро разрешены, и мы получим обратно сопоставленный объект, который выглядит так же, как если бы он был встроен в наш главный документ.

Давайте посмотрим на код:

@DBRef
private EmailAddress emailAddress;

EmailAddress выглядит так:

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

    private String value;

    // standard getters and setters
}

Обратите внимание, что каркас отображенияdoesn’t handle cascading operations. Так, например, если мы запускаемsave для родителя, дочерний элемент не будет сохранен автоматически - нам нужно будет явно активировать сохранение для дочернего элемента, если мы хотим сохранить его.

Именно здесьlife cycle events come in handy.

3. События жизненного цикла

Spring Data MongoDB публикует некоторые очень полезные события жизненного цикла, такие какonBeforeConvert, onBeforeSave, onAfterSave, onAfterLoad иonAfterConvert..

Чтобы перехватить одно из событий, нам нужно зарегистрировать подклассAbstractMappingEventListener и переопределить один из методов здесь. Когда событие отправлено, наш слушатель будет вызван и передан объект домена.

3.1. Базовое каскадное сохранение

Давайте посмотрим на предыдущий пример - сохранениеuser с помощьюemailAddress. Теперь мы можем прослушать событиеonBeforeConvert, которое будет вызвано перед тем, как объект домена попадет в конвертер:

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


Теперь нам просто нужно зарегистрировать слушателя вMongoConfig:

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

Или как XML:

И у нас есть каскадная семантика, все сделано - хотя только для пользователя.

3.2. Общая реализация каскада

Давайте теперь улучшим предыдущее решение наmaking the cascade functionality generic.. Начнем с определения пользовательской аннотации:

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

Давайте теперьwork on our custom listener будет обрабатывать эти поля в общих чертах и ​​не будет приводить их к какой-либо конкретной сущности:

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


Итак, мы используем утилиту отражения из Spring и выполняем обратный вызов для всех полей, соответствующих нашим критериям:

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

Как видите, мы ищем поля с аннотациейDBRef иCascadeSave. Как только мы находим эти поля, мы сохраняем дочернюю сущность.

Давайте посмотрим на классFieldCallback, который мы используем, чтобы проверить, есть ли у ребенка аннотация@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;
    }
}

Наконец, чтобы все это работало вместе, нам, конечно, нужно, чтобы полеemailAddress теперь было правильно аннотировано:

@DBRef
@CascadeSave
private EmailAddress emailAddress;

3.3. Каскадный тест

Давайте теперь рассмотрим сценарий - мы сохраняемUser с помощьюemailAddress, и операция сохранения автоматически передается в этот встроенный объект:

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

Давайте проверим нашу базу данных:

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

4. Заключение

В этой статье мы проиллюстрировали некоторые интересные функции Spring Data MongoDB - аннотацию@DBRef, события жизненного цикла и то, как мы можем разумно обрабатывать каскадирование.

Реализация всех этих примеров и фрагментов кодаcan be found over on GitHub - это проект на основе Maven, поэтому его должно быть легко импортировать и запускать как есть.

Related