Каскадные операции. Часть 1

Введение

Каскадные операции представляют собой процес сохранения объекта таким образом, что если объект имующий вложенные сущности подвергнется изменению, то вложенные объекты, так же будут подвергнуты изменению. Приведём простой пример: При удалении директории, мы так же удаляем и файлы, вложенные в этоу директорию. При изменении имени директории, файлы так же поменяют свой путь. При добавлении нового файла в директорию, изменится только файл и директория, при этом остальные файлы не будут иметь понятия, что собственно произошло. Таким образом работают и каскадные операции.

Что будем рассматривать

Основные каскадные операции, с которыми мы сегодня познакомимся суть следующие: PERSIST, MERGE, REMOVE, REFRESH. Существует ещё две каскадные операции ALL, DETACH, но они выходят за рамки сегодняшнего обсуждения и если мы их коснёмся, то только всколзь. Стоит так же отметить операцию orphanRemoval о которой мы так же сегодня поговорим.

Что это такое я вас спрашиваю?

При разработке любой системы, имеющей доступ к сохранению данных в базу данных, вам не избежать связей таблиц друг с другом. Если же попробовать представить базу данных в виде объектов разрабатываемой системы, то в итоге мы получим то, что называется ORM (Object Relation Mapping) в котором взаимосвязи таблиц будут представлены полями объектов. Поля же могут иметь разную степень связанности, один к одному, один ко многим, многие ко многим и многие к одному. Для того, чтобы определить правила, по которым конкретное поле будет работать в JPA определны несколько аннотаций: @OneToOne, @OneToMany, @ManyToMany и @ManyToOne Каждая такая аннотация обладает набором свойств, которые регламентируют поведение при операциях с базой данных. Именно о таком поведении мы и будем говорить.

Примеры

Перед нами стоит задача реализовать статью для блога. Статья же может иметь тег, по которому потом её можно будет легко найти. Итак, перед нами два объекта


@Entity
public class Article {
    ...
    @OneToOne
    private Tag tag;
    ...
}

@Entity
public class Tag {
   ...
   @OneToOne(mappedBy="tag")
   private Article article;
   ...
}

Тут мы видим, объектное представление наших таблиц, для которых установлена связь один к одному. По умолчанию аннотация @OneToOne настроена на сохранение detached элемента, а соответственно, для начала нам придётся сохранить тег, а уже потом статью. Это будет выглядеть примерно так:


...
tag = tagRepository.save(tag);
article.setTag(tag);
articleRepository.save(article);
...

Если же мы попробуем сохранить объект тег в состоянии transient мы получим исключение IllegalStateException которое предупредит нас, что нельзя сохранять объект, которого ещё не существует в системе. (Подробнее об этом вы можете почитать здесь). Данная конфигурация подходит для случаев, когда статья использует существующие теги и ими управляет. Если же от нас требуется создавать новый тег, то этот метод реализации нам не подходит, потому переделаем аннотацию под новые условия:


@Entity
public class Article {
    ...
    @OneToOne(cascade = {CascadeType.PERSIST})
    private Tag tag;
    ...
}

В этой реализации, мы установили связь с объектом тег так, что при сохранении объекта статья, статья сама будет управлять сохранением своих тегов, однако и в этой реализации есть свои недостатки. Рассмотрим их. Во-первых, мы сохраняем объекты в состоянии transient, во-вторых, при удалении статьи, останется тег так и останется в системе, так сказать в осиротевшем состоянии. Нам этого конечно не нужно. Для того, чтобы исправить второй момент, просто добавим свойство orphanRemoval и установим ему значение true, что заставит статью полностью управлять объектом тег и в случае удаления первого, так же будет удалён и второй. Так сказать подчистил за собой. Выглядит же это всё так:


@Entity
public class Article {
    ...
    @OneToOne(cascade = {CascadeType.PERSIST}, orphanRemoval=true)
    private Tag tag;
    ...
}

С первым же недоразумением справиться отнюдь не так просто. Но также возможно. Можно предположить, что с добавлением нового каскада: CascadeType.MERGE, мы решим существующую проблему, то спешу вас огорчить, это не так. Мы будем получать исключение PersistenceException, которое известит нас о том, что нельзя сохранять detached объекты. Решить данную проблему можно изменением состояния объекта в persistence, а этого можно добиться если выполнить операцию в транзакции. Следовательно, наш код будет выглядеть следующим образом:


@Transactional
...
tag = tagRepository.getOne(tag.getId());
article.setTag(tag);
articleRepository.save(article);

Примерно таким образом реализуется эта задача. Возможно существуют другие варианты, если они есть, прошу указать мне на мои ошибки здесь.

spring jpa
© JavaSE.ru