При создании двунаправленной связи между двумя классами-сущностями мы должны самостоятельно управлять жизненными циклами объектов каждого класса, в том числе сохранением строк в БД. С помощью настройки CascadeType.PERSIST в Hibernate можно избавиться от иногда рутинной операции сохранения дочерних сущностей сразу после сохранения родительской.
Подготовка
Создадим минимальное Spring Boot приложение с поддержкой JPA/Hibernate.
Код
Создадим двунаправленную связь между двумя классами-сущностями. Для этого сперва создадим класс предметной области следующего содержания:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
@Entity public class Address { @Id @GeneratedValue private Long id; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "PERSON_ID", nullable = false) private Person person; @Column(nullable = false) private String city; @Column(nullable = false) private String street; @Column(nullable = false) private String house; //Конструкторы, геттеры и сеттеры, equals(), hashCode() и т.д. } |
А затем следующий класс-сущность:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
@Entity public class Person { @Id @GeneratedValue private Long id; private String name; private Integer age; @OneToMany(mappedBy = "person", fetch = FetchType.LAZY, cascade = CascadeType.PERSIST) private List<Address> addresses; //Конструкторы, геттеры и сеттеры, equals(), hashCode() и т.д. } |
Подробное описание аннотаций и их параметров, используемых при создании двунаправленной связи, см. в соответствующей статье.
Мы передаём в параметр cascade аннотации @OneToMany значение CascadeType.PERSIST, указывая Hibernate’у автоматически сохранять в БД данные списка addresses при сохранении объекта Person.
Так как класс Address объявлен сущностью с помощью аннотации @Entity, он имеет собственный репозиторий и объекты этого класса имеют собственный жизненный цикл, который управляется отдельно. Тот факт, что между классами Person и Address есть связь один-ко-многим, ни на что не влияет. Мы должны всякий раз вызывать addressRepository.save(..)
, когда хотим сохранить в БД данные любого объекта Address.
Однако параметр CascadeType.PERSIST позволяет нам несколько упростить нашу работу, если мы хотим, чтобы Hibernate автоматически сохранял состояние элементов коллекции addresses, когда мы сохраняем сам объект типа Person.
Далее создадим репозиторий для класса Address:
1 2 3 4 |
@Repository public interface AddressRepository extends JpaRepository<Address, Long> { List<Address> findByPerson(Person person); } |
Далее создадим репозиторий для класса Person:
1 2 3 |
@Repository public interface PersonRepository extends JpaRepository<Person, Long> { } |
Напишем тестовый метод, который продемонстрирует работу настроек:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
@SpringBootTest @Transactional class TestApplicationTests { @Autowired PersonRepository personRepository; @Autowired AddressRepository addressRepository; @Test void cascadePersistTest() { Person irina = new Person("irina", 28); List<Address> irinasAddresses = List.of( new Address(irina, "Москва", "Садовая", "12"), new Address(irina, "Воскресенск", "СНТ Строитель", "424") ); irina.setAddresses(irinasAddresses); personRepository.save(irina); List<Address> addressesInDb = addressRepository.findByPerson(irina); //Благодаря CascadeType.PERSIST адреса сохранились вместе с irina assertTrue(addressesInDb.containsAll(irinasAddresses)); } } |
В данном методе мы создаём объект irina Типа Person и список из двух объектов Address. Мы устанавливаем ссылки объектов друг на друга. У каждого объекта Address поле person заполняется ссылкой на объект irina в конструкторе. Затем список Address устанавливается в поле addresses объекта irina через сеттер: irina.setAddresses(irinasAddresses)
.
Затем мы сохраняем только объект irina.
Затем мы извлекаем данные, соответствующие объекту irina, из таблицы ADDRESS и видим, что данные обоих объектов Address сохранились, несмотря на то, что мы явно не вызывали addressRepository.save(..)
. Если бы не настройка CascadeType.PERSIST в талицу ADDRESS ничего бы не сохранилось.