使用@IdClass声明复合主键时JPA编写@Query按主键集合批量删除及级联问题
核心问题解答
你使用@IdClass声明复合主键的场景下,EclipseLink支持JPQL行值表达式直接匹配复合主键,不需要先查询实体即可实现批量删除,正确的代码写法如下:
@Repository public interface MyCRUDRepository extends CrudRepository<MyEntity, MyIdClass> { @Modifying @Transactional // 可在调用层统一加事务注解 @Query("DELETE FROM MyEntity m WHERE (m.foo, m.bar) IN :ids") void deleteAllWithIds(Collection<MyIdClass> ids); }
这里的(m.foo, m.bar)和MyIdClass中定义的两个主键字段顺序完全对应,JPA会自动把入参集合里的每个MyIdClass实例拆成两个字段做匹配,最终生成的SQL类似DELETE FROM myentity WHERE (foo, bar) IN ((?,?), (?,?), ...),全程没有额外的SELECT查询,完全符合你规避性能开销的要求。
次要问题解答
不会触发CASCADE配置的关联删除。因为@Query编写的批量JPQL操作属于直接操作数据库的批量写操作,会跳过JPA的实体生命周期管理逻辑,也不会处理持久化上下文中的实体关联,所以CascadeType.ALL和orphanRemoval配置都不会生效。
如果需要同时删除关联的AnotherEntity,有两种可行方案:
- 先批量查询出待删除
MyEntity对应的anotherEntity.externalPk,先批量删除AnotherEntity,再执行MyEntity的批量删除 - 直接在数据库层给
myentity表的my_foreign_key字段添加数据库级别的外键级联删除约束,由数据库自动处理关联删除,不需要JPA参与
EclipseLink批量机制补充说明
for循环调用deleteById一定会导致批量机制失效:
deleteById的默认实现会先执行findById查询实体,每次调用都会产生一条SELECT语句- EclipseLink的批量写入要求同类型操作连续执行才会攒批,SELECT和DELETE交替出现的场景下,每次查询都会触发未提交的DELETE语句刷入数据库,根本无法攒批,性能和单条执行没有区别。
上面给出的批量@Query写法会直接生成一条批量DELETE SQL,是性能最优的实现,也能完美适配EclipseLink的优化机制。
内容的提问来源于stack exchange,提问作者payne
相关产品推荐
相关产品推荐

