首页 > 解决方案 > Spring Data problem - derived delete doesn't work

问题描述

I have a spring boot application (based off spring-boot-starter-data-jpa. I have an absolute minimum of configuration going on, and only a single table and entity.

I'm using CrudRepository<Long, MyEntity> with a couple of findBy methods which all work. And I have a derived deleteBy method - which doesn't work. The signature is simply:

public interface MyEntityRepository<Long, MyEntity> extends CrudRespository<> {
    Long deleteBySystemId(String systemId);
    // findBy methods left out
}

The entity is simple, too:

@Entity @Table(name="MyEntityTable")
public class MyEntity {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="MyEntityPID")
    private Long MyEntityPID;

    @Column(name="SystemId")
    private String systemId;

    @Column(name="PersonIdentifier")
    private String personIdentifier;

   // Getters and setters here, also hashCode & equals.
}

The reason the deleteBy method isn't working is because it seems to only issue a "select" statement to the database, which selects all the MyEntity rows which has a SystemId with the value I specify. Using my mysql global log I have captured the actual, physical sql and issued it manually on the database, and verified that it returns a large number of rows.

So Spring, or rather Hibernate, is trying to select the rows it has to delete, but it never actually issues a DELETE FROM statement.

According to a note on Baeldung this select statement is normal, in the sense that Hibernate will first select all rows that it intends to delete, then issue delete statements for each of them.

Does anyone know why this derived deleteBy method would not be working? I have @TransactionManagementEnabled on my @Configuration, and the method calling is @Transactional. The mysql log shows that spring sets autocommit=0 so it seems like transactions are properly enabled.

I have worked around this issue by manually annotating the derived delete method this way:

public interface MyEntityRepository<Long, MyEntity> extends CrudRespository<> {
    @Modifying
    @Query("DELETE FROM MyEntity m where m.systemId=:systemId")
    Long deleteBySystemId(@Param("systemId") String systemId);
    // findBy methods left out
}

This works. Including transactions. But this just shouldn't have to be, I shouldn't need to add that Query annotation.

Here is a person who has the exact same problem as I do. However the Spring developers were quick to wash their hands and write it off as a Hibernate problem so no solution or explanation to be found there.

Oh, for reference I'm using Spring Boot 2.2.9.

标签: spring-boothibernatespring-data-jpaspring-data

解决方案


tl;博士

这一切都在参考文档中。这就是 JPA 的工作方式。(我搓手洗手。

细节

这两种方法做了两件不同的事情:Long deleteBySystemId(String systemId);通过给定的约束加载实体并最终发布EntityManager.delete(…)持久性提供者将延迟到事务提交之前的发布。即,该调用之后的代码不能保证更改已经同步到数据库。这反过来又是由于 JPA 允许其实现真正做到这一点。不幸的是,Spring Data 无法解决此问题。(更多的摩擦,更多的洗涤,加上一点肥皂。

参考文档证明了这种行为需要EntityManager(同样是 JPA 抽象,与 Spring Data 无关)来触发@PreDelete用户期望触发的生命周期事件等。

手动声明修改查询的第二种方法是声明要在数据库中执行的查询,这意味着实体生命周期不会触发,因为实体没有预先实现。

然而,Spring 开发人员很快就洗手并将其作为 Hibernate 问题注销,因此在那里找不到解决方案或解释。

有详细的解释为什么它的工作方式在票的评论中工作。甚至提供了解决方案。解决方法和建议,通过控制此行为的堆栈部分提出此问题。(关上水龙头,伸手去拿毛巾。


推荐阅读