1. 概述
在本教程中,我们将了解如何在Spring Data JPA中完成删除操作。
2. 简单实体
正如我们从Spring Data JPA参考文档中了解到的,Repository接口为我们提供了一些对实体的基本支持。
假设我们有一个实体,例如Book:
@Entity
public class Book {
@Id
@GeneratedValue
private Long id;
private String title;
// standard constructors ...
// standard getters and setters ...
}
然后我们可以扩展Spring Data JPA的CrudRepository接口,让我们可以访问Book上的CRUD操作:
@Repository
public interface BookRepository extends CrudRepository<Book, Long> {
}
3. Repository的删除方法
其中,CrudRepository包含两种删除方法:deleteById和deleteAll。
让我们直接从BookRepository测试这些方法:
@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = {Application.class})
class DeleteFromRepositoryUnitTest {
@Autowired
private BookRepository repository;
Book book1;
Book book2;
@BeforeEach
void setup() {
book1 = new Book("The Hobbit");
book2 = new Book("All Quiet on the Western Front");
repository.saveAll(Arrays.asList(book1, book2));
}
@AfterEach
void teardown() {
repository.deleteAll();
}
@Test
void whenDeleteByIdFromRepository_thenDeletingShouldBeSuccessful() {
repository.deleteById(book1.getId());
assertThat(repository.count()).isEqualTo(1);
}
@Test
void whenDeleteAllFromRepository_thenRepositoryShouldBeEmpty() {
repository.deleteAll();
assertThat(repository.count()).isEqualTo(0);
}
}
即使我们使用的是CrudRepository,但请注意,对于其他Spring Data JPA接口(例如JpaRepositories或PagingAndSortingRepository),也存在这些相同的方法。
4. 派生删除方法
我们还可以派生用于删除实体的查询方法。这些方法有一套编写规则,但我们只关注最简单的示例。
派生的删除方法必须以deleteBy开头,后跟选择条件的名称。这些条件必须在方法调用中提供。
假设我们想按标题删除Book对象。根据命名约定,我们将以deleteBy开头并将title列为我们的标准:
@Repository
public interface BookRepository extends CrudRepository<Book, Long> {
long deleteByTitle(String title);
}
long类型的返回值表示该方法删除了多少条记录。
让我们编写一个测试并确保它是正确的:
@Test
@Transactional
void whenDeleteFromDerivedQuery_thenDeletingShouldBeSuccessful() {
long deletedRecords = bookRepository.deleteByTitle("The Hobbit");
assertThat(deletedRecords).isEqualTo(1);
}
在JPA中持久化和删除对象需要一个事务。这就是为什么在使用这些派生的删除方法时应该使用@Transactional注解,以确保在事务中运行。这在Spring与ORM文档中有详细说明。
5. 自定义删除查询
派生查询的方法名称可能会很长,并且仅限于单个表。
当我们需要做更复杂的查询时,我们可以一起使用@Query和@Modifying注解来编写自定义查询。
让我们使用这些注解编写之前派生方法的等效代码:
@Modifying
@Query("delete from Book b where b.title=:title")
void deleteBooks(@Param("title") String title);
同样,我们可以通过一个简单的测试来验证它是否有效:
@Test
@Transactional
void whenDeleteFromCustomQuery_thenDeletingShouldBeSuccessful() {
bookRepository.deleteBooks("The Hobbit");
assertThat(bookRepository.count()).isEqualTo(1);
}
上面介绍的两种解决方案都是相似的,并且实现了相同的结果。但是,它们采取的方法略有不同。
@Query注解方法针对数据库创建单个JPQL查询。相比之下,deleteBy方法执行读取查询,然后逐个删除每一项。
6. 包含映射关系的删除
现在让我们看看当我们与其他实体建立关系时会发生什么。
假设我们有一个Category实体,它与Book实体具有一对多的关联关系:
@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实体以反映这种关联:
@Entity
public class Book {
@Id
@GeneratedValue
private Long id;
private String title;
@ManyToOne
private Category category;
// setters getters and constructors ...
}
现在让我们添加两个Category,并将它们与当前拥有的Book相关联。
@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = DeleteApplication.class)
class DeleteInRelationshipsUnitTest {
@Autowired
private BookRepository bookRepository;
@Autowired
private CategoryRepository categoryRepository;
@BeforeEach
void setup() {
Book book1 = new Book("The Hobbit");
Category category1 = new Category("Cat1", book1);
categoryRepository.save(category1);
Book book2 = new Book("All Quiet on the Western Front");
Category category2 = new Category("Cat2", book2);
categoryRepository.save(category2);
}
@AfterEach
void teardown() {
bookRepository.deleteAll();
categoryRepository.deleteAll();
}
}
现在如果我们尝试删除类别,书籍也将被删除:
@Test
void whenDeletingCategories_thenBooksShouldAlsoDeleted() {
categoryRepository.deleteAll();
assertThat(bookRepository.count()).isEqualTo(0);
assertThat(categoryRepository.count()).isEqualTo(0);
}
不过,这不是双向的,这意味着如果我们删除书籍,类别仍然存在:
@Test
void whenDeletingBooks_thenCategoriesShouldNotAlsoDeleted() {
bookRepository.deleteAll();
assertThat(bookRepository.count()).isEqualTo(0);
assertThat(categoryRepository.count()).isEqualTo(2);
}
我们可以通过更改关系的属性(例如CascadeType)来改变这种行为。
7. 总结
在本文中,我们看到了在Spring Data JPA中删除实体的不同方法。
我们查看了CrudRepository提供的删除方法以及我们的派生查询或使用@Query注解的自定义查询。
我们还看到了删除是如何在关联实体中完成的。
与往常一样,本教程的完整源代码可在GitHub上获得。