【问题标题】:Spring Data problem - derived delete doesn't workSpring Data 问题 - 派生删除不起作用
【发布时间】:2020-12-06 16:29:13
【问题描述】:

我有一个 spring boot 应用程序(基于 spring-boot-starter-data-jpa。我有一个绝对最少的配置,只有一个表和实体。

我正在使用 CrudRepository 和几个 findBy 方法,它们都可以工作。而且我有一个派生的 deleteBy 方法——它不起作用。签名很简单:

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

实体也很简单:

@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.
}

deleteBy 方法不起作用的原因是因为它似乎只向数据库发出“select”语句,该语句选择所有 MyEntity 行,其中 SystemId 具有我指定的值。使用我的 mysql 全局日志,我捕获了实际的物理 sql 并在数据库上手动发出它,并验证它返回了大量的行。

所以 Spring,或者更确切地说是 Hibernate,正在尝试选择它必须删除的行,但它实际上从未发出 DELETE FROM 语句。

根据note on Baeldung 的说法,这个选择语句是正常的,从某种意义上说,Hibernate 将首先选择它打算删除的所有行,然后为每个行发出删除语句。

有谁知道为什么这个派生的 deleteBy 方法不起作用?我的@Configuration 上有@TransactionManagementEnabled,调用的方法是@Transactional。 mysql 日志显示 spring 设置了 autocommit=0,所以看起来事务已正确启用。

我已经通过手动注释派生的删除方法来解决这个问题:

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
}

这行得通。包括交易。但这不应该是,我不应该添加那个 Query 注释。

Here is a person 和我有同样问题的人。然而,Spring 开发人员很快就洗手并将其作为 Hibernate 问题注销,因此在那里找不到解决方案或解释。

哦,作为参考,我使用的是 Spring Boot 2.2.9。

【问题讨论】:

  • Long deleteBySystemId(String systemId); 上添加@Transactional 应该可以工作
  • 您应该为您的存储库创建带有@Transactional@Service 注释的服务。
  • @DirkDeyne 是的,但它没有。结果相同。此外,我不希望我的存储库管理我的事务,我在使用存储库的服务方法中的其他地方有事务注释。此外,无论事务如何,都应该向数据库发出某种“DELETE”语句。没有。
  • @Seldo97 我有这些。这没什么区别。没有发出 DELETE 语句,只有一个 SELECT 语句

标签: spring-boot hibernate spring-data-jpa spring-data


【解决方案1】:

tl;博士

这一切都在reference documentation 中。这就是 JPA 的工作方式。 (我在搓洗手。

详情

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

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

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

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

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

【讨论】:

  • 我很高兴你的手现在很干净:-) 我知道这不是 Springs 本身的错误,而是底层的 JPA 实现。但是这种工作方式最终会给开发人员带来更多的麻烦,而不是更少。你给出的理由对我来说似乎是武断的技术问题。 “哦,预删除必须开火”。然后解雇预删除,有什么大不了的?为什么它延迟删除而不是插入?当 JPA 尝试在删除之前插入时,它会反转事务中数据库操作的顺序。这在世界上怎么可能是合理的?它的用例在哪里?
  • 这些都是很好的问题。休眠团队。再说一遍:我们控制实现所有这些的代码。
猜你喜欢
  • 2018-10-27
  • 1970-01-01
  • 1970-01-01
  • 2014-12-27
  • 1970-01-01
  • 2016-02-14
  • 2020-09-12
  • 2018-07-02
  • 2020-03-15
相关资源
最近更新 更多