Где применять изменяющиеся поля, ведущие к копированию сущности по бизнес-правилу - PullRequest
1 голос
/ 21 января 2020

У меня есть проект spring-data-rest, где у меня есть какая-то сущность, например, под названием Aaa. Это упрощенное определение:

@Entity
@Data // some lombok-project magic for getters/setters/...
public class Aaa {
    // many different fields

    /**
     * bi-directional many-to-one association to Bbb
     */
    @ManyToOne(optional = true)
    @JoinColumn(name="bbb_fk")
    @RestResource(
        description = @Description("Optional relation of Aaa to Bbb. " +
            "If not empty, it means that this Aaa belongs to the given Bbb. " +
            "Otherwise given Aaa is just something like a template."
        ))
    private Bbb bbb;
    // also some other references, like:
    private List<Ccc> cccs;
}

Мне нужно (по бизнес-правилу) убедиться, что установка ссылки Bbb приведет к копии данного объекта в базе данных, и только копия будет иметь заданную ссылку. Семантика копирования при записи. Изменение ссылки с одного экземпляра Bbb на другой, не вызывает копирования.

Обратите внимание, что сущности Aaa, а также сущности Bbb имеют свои interface AaaRepository extends PagingAndSortingRepository<Aaa, Long> и BbbRepository. Это означает, что при использовании представления HAL экземпляр Aaa имеет в своем теле просто ссылку на ассоциацию с Bbb.

Цель / цель : я сохранил «шаблоны» экземпляров Aaa в таблице (например, Экземпляр Aaa, имеющий Aaa.bbb == null), а также "реальные" экземпляры Aaa (например, Aaa.bbb не имеет значение null). При создании «реального» экземпляра Aaa это ВСЕГДА делается с использованием некоторого шаблона Aaa. При установке Aaa.bbb со значением NULL, я хотел бы сделать копию данного Aaa и установить Aaa_copy.bbb на требуемое значение. Кроме того, возвращаемый ресурс rest должен быть вновь созданной копией (т. Е. Установка ассоциации для ресурса rest с идентификатором /api/aaa/123 вернет экземпляр с другим идентификатором!).

Возможные решения, о которых я думаю. Я не реализовал ни одного из них, я просто хочу выбрать правильный подход для реализации :

  1. Реализовать пользовательский контроллер для ассоциативной ссылки (т.е. / api / aaa / { id} / bbb для POST и PUT. Возможную проблему с «сокрытием» можно решить, возможно, легко.
  2. переопределить S save(S s) и метод saveAll в репозитории и сделать «клонировать, если» Нужно "magi c там
  3. реализовать метод в классе Aaa и аннотировать его с аннотацией @PrePersist.

Где (и почему там), я должен реализовать такой поведение?

Ответы [ 2 ]

1 голос
/ 22 января 2020

Пользователь хочет отредактировать ассоциацию Bbb объекта Aaa, то есть связать другой объект Bbb с рассматриваемым объектом Aaa. Вы хотели бы реализовать какой-либо контроль версий и сохранить копию объекта Aaa в состоянии, в котором он до изменения применяется.

I предложил бы следующие решения для решения этой проблемы:

Обработка событий REST Spring Data

Использование Функциональность событий REST Spring Data и ...

Расширение AbstractRepositoryEventListener

... реализовать класс, расширяющий AbstractRepositoryEventListener, содержащий метод, переопределяющий метод onBeforeLinkSave(...).

@Component
public class AaaRepositoryListener extends AbstractRepositoryEventListener<Aaa> {
    @Override
    protected void onBeforeLinkSave(Aaa parent, Object linked) {
        // Handle event, remember to detach the entity using the entity manager if necessary and checking the type of the linked object.
    }
}

Аннотирование с помощью @RepositoryEventHandler

... реализовать класс, помеченный @RepositoryEventHandler, содержащий метод, который обрабатывает BeforeLinkSaveEvent.

@Component
@RepositoryEventHandler 
public class AaaEventHandler {
  @PersistenceContext
  private EntityManager entityManager;

  @HandleBeforeLinkSave
  public void handleAaaToBbbSave(Aaa aaa, Bbb bbb) {
    // Mind that this only handles changes on Aaa objects
    // that affect Bbb links and only takes a single argument.
    // As soon as Aaa contains links to other classes, this method
    // no longer works.
    // 
    // Copy Aaa object and store it in the repository.
  }
}

Заметки о вышеуказанных методах

Пожалуйста, помните, что объект, полученный в методе handleAaaToBbbSave(...), может быть прикреплен, и вам может потребоваться отсоединить его (EntityManager.detach(...)) перед сбросом идентификатора и повторным сохранением.

Кроме того, из-за ошибки в Spring Data REST вам необходимо добавить этот компонент в ваше приложение Таким образом, события на самом деле обрабатываются.

@Configuration
public class BugFixForSpringDATAREST524 implements InitializingBean {

    private ValidatingRepositoryEventListener eventListener;
    private Map<String, Validator>            validators;

    @Autowired
    public BugFixForSpringDATAREST524(ValidatingRepositoryEventListener eventListener,
                                      Map<String, Validator> validators) {
        this.eventListener = eventListener;
        this.validators    = validators;
    }

    @Override
    public void afterPropertiesSet() {
        List<String> events = Arrays.asList("beforeCreate",
                                            "afterCreate",
                                            "beforeSave",
                                            "afterSave",
                                            "beforeLinkSave",
                                            "afterLinkSave",
                                            "beforeDelete",
                                            "afterDelete");

        for (Map.Entry<String, Validator> entry : validators.entrySet()) {
            events.stream()
                    .filter(p -> entry.getKey().startsWith(p))
                    .findFirst()
                    .ifPresent(p -> eventListener.addValidator(p, entry.getValue()));
        }
    }
}

Обратите внимание, что события запускаются, только если Spring Data REST действительно используется. Если в репозитории используется метод save(...), событие не инициируется, и копия затронутого объекта Aaa не сохраняется.

Spring AOP (Аспектно-ориентированное программирование)

Если вы хотите поддержать метод save(...) хранилища, я рекомендую использовать Spring AOP для создания @Before или @Around совета (зависит от ваших потребностей) для перехвата вызова на метод хранилища. Вот базовые c строительные леса такого компонента:

@Aspect
@Component
public class AaaRepositoryAspect {

    @Pointcut(value = "execution(* com.example.backend.repository.aaa.AaaRepository.save()) && args(aaa)")
    private void repositorySave(Aaa aaa) {
    }


    @Before(value = "repositorySave(aaa)")
    private void beforeSave(Aaa aaa) throws Throwable {
        // Save a copy of the object.
    }
}

Обоснование

До Почему Я рекомендую вышеуказанные методы вместо один из ваших методов:

  1. Вам потребуется создать контроллер, который переопределит метод, представленный Spring Data REST. Кроме того, вам нужно обработать возвращаемые значения и (если вы, например, используете Spring HATEOAS) собрать ресурсы самостоятельно. Кроме того, это относится только к вызовам к конечной точке, а не к внутренним вызовам метода save(...) хранилища.

  2. Опять же, вам нужно написать много кода, который вы на самом деле делаете не нужно.

  3. Это создает зависимость между вашей моделью и вашими репозиториями, потому что вам нужен экземпляр репозитория в вашем классе модели.

Использование обработчика событий, предоставляемого Spring Data REST, сохраняет код, который вы используете для управления версиями, близко к хранилищу и в Spring Data REST. Использование аспекта аналогично, это просто более абстрактная версия обработчика событий (без учета фактической реализации).

0 голосов
/ 12 марта 2020

Существует новая возможность, называемая BeforeSaveCallback ( страница документации ) и BeforeConvertCallback ( страница документации ) в весеннем выпуске Moore. Можно использовать что-то вроде этого:

@Bean
BeforeSonvertCallback<Aaa> beforeSave() {
  return (aaa, convertedAaa) -> {
    // aaa.modifyBeforeSave...
    // perhaps do something like this:
    // aaa = new Aaa(aaa.Bbb, null);
    return aaa;
  }
}

Для получения дополнительной информации посмотрите 23-минутную https://www.infoq.com/presentations/spring-data-enhancements/ видеопрезентацию.

...