1. 概述

本文将探讨 Spring Data JPA 中的删除操作,包括如何通过 Repository 删除实体、使用派生删除查询、自定义删除语句,以及处理实体之间的关联关系时的删除行为。

如果你已经对 Spring Data JPA 有基本了解,并且在项目中使用过 CRUD 操作,那么这篇文章会帮助你更好地掌握删除操作的细节,避免踩坑。


2. 示例实体

根据 Spring Data JPA 官方文档,我们可以通过继承 CrudRepository 接口快速获得对实体的增删改查能力。

我们以 Book 实体为例:

@Entity
public class Book {

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

    // 构造函数、getter、setter 省略
}

然后定义一个 BookRepository

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

这样我们就拥有了对 Book 实体的基本操作能力。


3. 通过 Repository 删除

CrudRepository 提供了两个删除方法:

  • deleteById(ID id)
  • deleteAll()

我们来写一个单元测试验证删除行为:

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

    @Autowired
    private BookRepository repository;

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

    // 初始化数据

    @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);
    }
}

⚠️ 注意:这些方法在 JpaRepositoryPagingAndSortingRepository 中也存在。


4. 派生删除查询

除了基本的删除方法,我们还可以通过方法名派生删除逻辑。

命名规则是: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);
}

✅ 注意:删除操作需要事务支持,所以必须加上 @Transactional 注解。


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 是一次性执行删除语句
  • deleteBy 是先查询出数据,再逐个删除

6. 删除与关联关系

当实体之间存在关联关系时,删除行为会受到级联设置的影响。

我们以 CategoryBook 之间的 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;

    // 构造函数、getter、setter 省略
}

同时修改 Book 实体:

@ManyToOne
private Category category;

并定义一个 CategoryRepository

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

测试删除行为:

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

✅ 删除 Category 时,Book 也会被级联删除(因为我们设置了 cascade = CascadeType.ALL)。

但反过来则不行:

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

⚠️ 说明删除是单向的,除非你手动配置双向级联。


7. 总结

本文介绍了 Spring Data JPA 中几种常见的删除方式:

方法 说明
deleteById / deleteAll 最基础的删除方法
deleteByXxx 派生删除查询,适合简单条件
@Query + @Modifying 自定义删除语句,更灵活
级联删除 需配置 cascade 属性,影响关联实体行为

✅ 实际开发中,建议根据业务场景选择合适的删除方式,注意事务控制与级联设置,避免数据残留或误删。

所有代码示例已整理在 GitHub 上,地址为:https://github.com/eugenp/tutorials(路径:persistence-modules/spring-data-jpa-crud


原始标题:Spring Data JPA Delete and Relationships