为啥手动定义的 Spring Data JPA 删除查询不会触发级联?

Posted

技术标签:

【中文标题】为啥手动定义的 Spring Data JPA 删除查询不会触发级联?【英文标题】:Why does a manually defined Spring Data JPA delete query not trigger cascades?为什么手动定义的 Spring Data JPA 删除查询不会触发级联? 【发布时间】:2014-06-20 00:37:47 【问题描述】:

我有以下问题:当我尝试删除具有以下关系的实体时:

@OneToMany(mappedBy="pricingScheme", cascade=CascadeType.ALL, orphanRemoval=true)
private Collection<ChargeableElement> chargeableElements;

通过提供的删除方法使用CrudRepository,它会删除实体及其所有可收费元素,这很好。当我尝试使用自定义删除时出现问题:

@Modifying
@Query("DELETE FROM PricingScheme p WHERE p.listkeyId = :listkeyId")
void deleteByListkeyId(@Param("listkeyId") Integer listkeyId);

上面写着:

com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: 
  Cannot delete or update a parent row: a foreign key constraint fails
  (`listkey`.`chargeableelements`, CONSTRAINT `FK_pox231t1sfhadv3vy7ahsc1wt` 
  FOREIGN KEY (`pricingScheme_id`) REFERENCES `pricingschemes` (`id`))

为什么不允许我这样做? @Query 方法不支持级联属性吗?我知道我可以先findByListkeyId(…),然后使用标准删除方法删除持久实体,但这并不优雅。是否可以按照我尝试的方式使用自定义 @Query 方法?

【问题讨论】:

【参考方案1】:

这与 Spring Data JPA 无关,而是 JPA 指定它的工作方式(第 4.10 节 - “批量更新和删除操作”,JPA 2.0 规范):

删除操作仅适用于指定类及其子类的实体。它不会级联到相关实体。

如果您考虑一下,JPA 级联不是数据库级级联,而是由EntityManager 维护的级联。因此,EntityManager 需要知道要删除的实体实例及其相关实例。如果您触发查询,它实际上无法知道这些,因为持久性提供程序将其转换为 SQL 并执行它。因此,EntityManager 无法分析对象图,因为执行完全在数据库中进行。

可以在此处找到与此主题相关的问答over here。

【讨论】:

【参考方案2】:

我遇到了同样的问题,为了解决这个问题,我在 @Modifying 注释中添加了属性 clearAutomatically 并具有真值。

@Modifying(clearAutomatically = true)
@Query("delete User u where u.id = :userId")
void deleteUserById(@Param("userId") Long userId);

除此之外,删除实体的服务方法使用 @Transaction 注释,插入用户的方法也是如此。

【讨论】:

clearAutomatically 与数据库级联无关。而是清除 entityManager 上下文。

以上是关于为啥手动定义的 Spring Data JPA 删除查询不会触发级联?的主要内容,如果未能解决你的问题,请参考以下文章

Spring Data JPA - 手动插入后的密钥冲突

spring-data-jpa循环保存数据,为啥只保存了最后一条数据

为啥在 Spring Data JPA Repository 上的 save() 之后使用返回的实例?

Spring data jpa repo,为啥需要接口服务和服务实现

如何使用 Spring Data JPA 保存具有手动分配标识符的实体?

Spring Data JPA 审计不适用于带有 @Modifying 注释的 JpaRepository 更新方法,为啥?