Перейти к основному содержимому

Удаление Spring Data JPA и отношения

· 4 мин. чтения

1. Обзор

В этом руководстве мы рассмотрим, как выполняется удаление в Spring Data JPA .

2. Образец сущности

Как мы знаем из справочной документации Spring Data JPA , интерфейсы репозитория предоставляют нам некоторую базовую поддержку сущностей.

Допустим, у нас есть сущность, например Книга :

@Entity
public class Book {

@Id
@GeneratedValue
private Long id;
private String title;

// standard constructors

// standard getters and setters
}

Затем мы можем расширить CrudRepository Spring Data JPA, чтобы предоставить нам доступ к операциям CRUD над Book :

@Repository
public interface BookRepository extends CrudRepository<Book, Long> {}

3. Удалить из репозитория

Помимо прочего, CrudRepository содержит два метода: deleteById и deleteAll .

Давайте протестируем эти методы прямо из нашего BookRepository :

@RunWith(SpringRunner.class)
@SpringBootTest(classes = {Application.class})
public class DeleteFromRepositoryUnitTest {

@Autowired
private BookRepository repository;

Book book1;
Book book2;
List<Book> books;

// data initialization

@Test
public void whenDeleteByIdFromRepository_thenDeletingShouldBeSuccessful() {
repository.deleteById(book1.getId());
assertThat(repository.count()).isEqualTo(1);
}

@Test
public void whenDeleteAllFromRepository_thenRepositoryShouldBeEmpty() {
repository.deleteAll();
assertThat(repository.count()).isEqualTo(0);
}
}

И хотя мы используем CrudRepository , обратите внимание, что эти же методы существуют и для других интерфейсов Spring Data JPA, таких как JpaRepository или PagingAndSortingRepository .

4. Производный запрос на удаление

Мы также можем вывести методы запросов для удаления сущностей. Существует набор правил их написания, но давайте сосредоточимся на самом простом примере.

Производный запрос на удаление должен начинаться с deleteBy , за которым следует имя критерия выбора. Эти критерии должны быть указаны в вызове метода.

Допустим, мы хотим удалить книги по названию . Используя соглашение об именах, мы начнем с deleteBy и перечислим title в качестве наших критериев:

@Repository
public interface BookRepository extends CrudRepository<Book, Long> {
long deleteByTitle(String title);
}

Возвращаемое значение типа long указывает, сколько записей было удалено методом.

Давайте напишем тест и убедимся, что это правильно:

@Test
@Transactional
public void whenDeleteFromDerivedQuery_thenDeletingShouldBeSuccessful() {
long deletedRecords = repository.deleteByTitle("The Hobbit");
assertThat(deletedRecords).isEqualTo(1);
}

Для сохранения и удаления объектов в JPA требуется транзакция. Вот почему мы должны использовать аннотацию @Transactional при использовании этих производных запросов на удаление , чтобы убедиться, что транзакция выполняется. Это подробно объясняется в ORM с документацией Spring .

5. Пользовательский запрос на удаление

Имена методов для производных запросов могут быть довольно длинными, и они ограничены только одной таблицей.

Когда нам нужно что-то более сложное, мы можем написать собственный запрос, используя вместе @Query и @Modifying .

Давайте проверим эквивалентный код для нашего производного метода из предыдущего:

@Modifying
@Query("delete from Book b where b.title=:title")
void deleteBooks(@Param("title") String title);

Опять же, мы можем убедиться, что это работает с помощью простого теста:

@Test
@Transactional
public void whenDeleteFromCustomQuery_thenDeletingShouldBeSuccessful() {
repository.deleteBooks("The Hobbit");
assertThat(repository.count()).isEqualTo(1);
}

Оба решения, представленные выше, похожи и дают одинаковый результат. Однако они придерживаются несколько иного подхода.

Метод @Query создает один запрос JPQL к базе данных. Для сравнения, методы deleteBy выполняют запрос на чтение, а затем удаляют каждый из элементов один за другим.

6. Удалить в отношениях

Теперь давайте посмотрим, что происходит, когда у нас есть отношения с другими сущностями.

Предположим, у нас есть сущность « Категория », имеющая связь OneToMany с сущностью « Книга» :

@Entity
public class Category {

@Id
@GeneratedValue
private Long id;
private String name;

@OneToMany(mappedBy = "category", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Book> books;

// standard constructors

// standard getters and setters
}

CategoryRepository может быть просто пустым интерфейсом, расширяющим CrudRepository : ``

@Repository
public interface CategoryRepository extends CrudRepository<Category, Long> {}

Мы также должны изменить сущность Book , чтобы отразить эту ассоциацию:

@ManyToOne
private Category category;

Давайте теперь добавим две категории и свяжем их с книгами, которые у нас есть.

Теперь, если мы попытаемся удалить категории, книги также будут удалены :

@Test
public void whenDeletingCategories_thenBooksShouldAlsoBeDeleted() {
categoryRepository.deleteAll();
assertThat(bookRepository.count()).isEqualTo(0);
assertThat(categoryRepository.count()).isEqualTo(0);
}

Однако это не является двунаправленным, а это означает, что если мы удалим книги, категории останутся:

@Test
public void whenDeletingBooks_thenCategoriesShouldAlsoBeDeleted() {
bookRepository.deleteAll();
assertThat(bookRepository.count()).isEqualTo(0);
assertThat(categoryRepository.count()).isEqualTo(2);
}

Мы можем изменить это поведение, изменив свойства отношения, такие как CascadeType .

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

В этой статье мы увидели разные способы удаления объектов в Spring Data JPA.

Мы рассмотрели предоставленные методы удаления из CrudRepository , а также наши производные или пользовательские запросы с использованием аннотации @Query .

Мы также видели, как удаление выполняется в отношениях.

Как всегда, все фрагменты кода, упомянутые в этой статье, можно найти в нашем репозитории GitHub .