【问题标题】:Spring-boot unwanted SQL UPDATE at the end of transaction事务结束时 Spring-boot 不需要的 SQL UPDATE
【发布时间】:2017-12-11 03:41:58
【问题描述】:

我正在使用 spring-boot 1.5.4 和 hibernate-core 5.2.10。

我有一个控制器,它调用服务方法(让我们将其命名为 pService)以在某些逻辑之后保存实体

pService.save 是这样的:

@Transactional(readOnly = false, rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public <S extends SomeEntity> S save(S entity) throws ServiceException {
    //some logic

    entity.setAttrX("original text");
    S justSavedEntity = someEntityRepository.save(entity); // POINT 1

   //we termporarily change one attribute just to get a legible representation calling another service method (lets name it eService)
   eService.process(justSavedEntity);

   return justSavedEntity; //POINT 2

}

eService.process 是这样的:

@Transactional(readOnly = false, propagation = Propagation.MANDATORY)
public void process(SomeEntity entity) {

    //some logic
    entity.setAttrX("someText");
    //method ends here
}

因为在 POINT 1 处没有其他调用来保存其中的一个,所以我希望将文本“原始文本”保存在数据库中,但我要保存的文本是在 POINT 2 处更改的文本。

那么,为什么我保存的是“someText”而不是“原始文本”?

我可以在数据库日志中看到,在 POINT 1 之后运行 SQL INSERT 命令,当 pService 返回时,运行不需要的 SQL UPDATE 命令,将“原始文本”更改为“someText”导致错误。

为什么会发出这个“额外”的 UPDATE 命令?

提前致谢!

【问题讨论】:

    标签: hibernate spring-boot java-8 spring-data-jpa spring-transactions


    【解决方案1】:

    这是 JPA 规范所描述的预期行为。您更改了一个 托管 实体,这在逻辑上与更改存储的数据相同,因此在事务完成之前,实体的状态与数据库同步。这个想法是您不必在方法结束时调用 save —— 这是多余的。

    Arnold Galovics 写了一封带有漂亮图表的 pretty good blog post about this

    鉴于此,很明显您不应该以这种方式改变实体。它表明您正在调用的“过程”没有将它作为实体传递的对象处理。一个常见的错误是认为仅仅因为您当前的对象似乎在不同的上下文中提供了您需要的一切,所以在该上下文中使用它是一个好主意,而不是检查该对象是否代表相同的概念。在这种情况下,您的流程不需要实体。

    您应该创建某种类型的值对象以传递给 process 方法。创建一个值对象就像从它的持久化上下文中分离实体一样简单:Spring JpaRepository - Detach and Attach entity

    或者,您可以只计算字段的值并将其作为第二个参数传递给您在 eService 上的流程方法。您的最终选择是按原样发送实体并让接收服务计算“清晰”表示。

    但是,您还有其他问题。如果您转换到 spring-data-jpa 而不是编写自己的存储库类,您可能会意识到它们是什么。简单来说,存储库类旨在在某种持久存储中存储和检索实体,而不是用于调用其他服务。这将是任何代码首先使用回购的责任。在 spring-mvc 应用程序中,这通常是一个控制器,用于编排您的应用程序服务。

    说白了,你不应该从 repo 类中调用服务。它应该对实体执行除 CRUD 之外的任何操作——绝对不会做其他任何事情。如果您需要它来做其他事情,那么您可能做错了其他事情,或者您为您的应用选择了错误的架构。

    你应该看看 spring-data-jpa。它默认配置为在运行时自动生成 repo 的实现。这可能会减少您的代码库,为您节省大量时间和缺陷,并且不会轻易让您犯此类架构错误。而且,最重要的是,它比您现在所做的要容易得多。

    【讨论】:

    • 非常感谢您的回答。这很奇怪,因为我们只在升级到 spring-boot 1.5、1.1 和 1.2 后才看到这种行为,需要一个 repository.save 调用来持久化一个实体。任何想法发生了什么变化?
    • 对于您所看到的情况有太多可能的解释,因此对您问题的任何回答都纯粹是推测性的。但是您可能应该知道,这种行为是核心 JPA 规范的一部分,并且一直在 spring 的 jpa 子系统中以这种方式实现。
    • 另外,我已经编辑了答案,在最后添加了一些相关的内容。
    • 并且,请随时将答案标记为正确;)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-26
    • 1970-01-01
    • 2020-03-11
    • 2021-05-17
    • 1970-01-01
    相关资源
    最近更新 更多