null

Немного о каскадирование в Spring Data JPA

​​​​​​​

Хочу рассказать несколько слов про каскадирование в Spring Data JPA. Всё что я говорю относится точно также и к JPA. Скорее рассказ будет про JPA, но ради удобства я приведу кусочки кода с Spring Data.  Сразу скажу, что рассказ будет неполным и где-то могут проскользнуть мои собственные ошибки в понимании этой темы. Суть статьи в том, чтобы показать вам несколько базовых примеров, когда стоит использовать каскадирование и перестать делать лишние запросы к БД и лишне строки в коде.

Сразу переходим к делу. Есть две сущности:  клиент и банковский счёт. Давайте сделаем предположение, что в нашей концептуальной модели не может существовать счёта без клиента. Когда человек приходит в банк и его заводят в БД, то ему сразу же создают счёт. Если человека удаляют из базы, то счёт удаляется вместе с ним. А теперь давайте представим, что у нас уже есть эти сущности, два репозитория и класс ClientService.

Ниже слегка упрощенный код сущностей:

 

@Data
@Entity
class Client {
   //Какие-то поля
  @OneToOne
  private BankAccount account;

}

 

@Data
@Entity
class Account {
   //Какие-то поля
  @OneToOne(mappedBy="account")
  private Client owner;

}

Отлично. А теперь посмотрим на метод создания клиента в соответствующем сервисе.

@Service
class ClientService {
   @Autowired
   private ClientRepository clientRepository;
  @Autowired
   private AccountRepository accountRepository;

  public void createClient(Client client) {
  Account account = new Account();
  //Заполняем ещё как-то аккаунт и делаем что-нибудь ещё
  account = accountRepository.save(account);
  client.setAccount(account);
  client = clientRepository.save(client);
  }
}

Если вы видете такой код, то знайте что это не ок. Мы делаем два вызова save(). Но можно ограничиться и одним. Для этого к нам приходит такая штука из JPA как каскадирование. По сути каскадность позволяет нам настроить наши связи между сущностями. Если быть конкретнее, то мы можем выстраить зависимость между сущностями.  Одна из сущностей будет выступать в роли стержневой, а другой в роли ассоциации. В JPA есть разные виды зависимостей.

Они описываются в перечислении CascadeType. Например, есть тип каскадирования CascadeType.DELETE. Если установить каскадирование от сущности Client к сущности Account тип каскадирование CascadeType.DELETE, то это будет обозначать что удаление сущности Client, автоматически приведёт к удалению из БД сущности Account. Но не наоборот! Запомните что каскадирование действует только в одном направлении.

Вот список всех типов каскадирования:

  • ALL
  • PERSIST
  • MERGE
  • REMOVE
  • REFRESH
  • DETACH​​​​​​​

В нашем конкретном примере нас интересует тип ALL, но давайте перед этим обсудим тип PERSIST. Этот тип каскадирование распостраняется на соответствующую операцию persist в JPA. По сути это создание новой сущности, которая ещё не была представлена в БД. Или на языке SQL - это insert. Если мы построим каскадирование на операцию вставки в БД, то это будет значить, что нам достаточно сказать о необходимости сохранения в БД только родительской сущности, а дочерняя (если она стоит в соответствующем поле родительской) сохранится в БД автоматически. С этим типом сущности нужно быть аккуратным. Например если вы прикрепляете к сущности уже имеющуюся в базе данных сущность. Например вы прикрепляете менеджера к клиентку. Но мне кажется, что в случае OneToOne такого быть не должно.

Теперь перейдем к тому как будет выглядеть код с использованием каскадирования.

 

@Data
@Entity
class Client {
   //Какие-то поля
  @OneToOne(cascade = CascadeType.ALL)
  private BankAccount account;

}

 

@Data
@Entity
class Account {
   //Какие-то поля
  @OneToOne(mappedBy="account")
  private Client owner;

}

По сути всё что мы сделали в сущностях - добавили атрибут в аннотацию @OneToOne в классе родительской сущности. А вот так будет выглядеть код сервиса.

@Service
class ClientService {
   @Autowired
   private ClientRepository clientRepository;

  public void createClient(Client client) {
  Account account = new Account();
  //Заполняем ещё как-то аккаунт и делаем что-нибудь ещё
  client.setAccount(account);
  client = clientRepository.save(client);
  }
}

​​​​​​​Мы убрали ненужный вызов метода save. Может показаться что это мелочь, но если бы вы использовали аннотацию @OneToMany, то вы бы избавились от бесмысленного цикла с установкой дочерним сущностям поля родительской и  не делали бы лишний вызов save или saveAll. А что если у вас ещё многоэтажная зависимость когда А зависит от Б, а от Б зависит С и т.п. Тогда вам покажется каскадированиео чень удобным.  Пользуйтесь каскадированием. Не пишите сложный код.

На этом, пожалуй, откланяюсь!