我使用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中的一个,我希望文本&#34;原始文本&#34;要保存在数据库中,但我保存的文本是在POINT 2处更改的文本。
那么,为什么我得到了拯救&#34; someText&#34;而不是&#34;原始文本&#34;?
我可以在数据库日志中看到,在POINT 1之后运行了一个SQL INSERT命令,并且当pService返回时,运行了不需要的SQL UPDATE命令,更改了原始文本&#34; to&#34; someText&#34;导致错误。
为什么这是&#34;额外&#34;发出UPDATE命令?
提前致谢!
答案 0 :(得分:2)
这是JPA规范中描述的预期行为。您更改了托管实体,该实体在逻辑上与更改存储数据的内容相同,因此在事务完成之前,实体的状态将与数据库同步。这个想法是你不必在方法结束时调用save - 它是多余的。
Arnold Galovics用漂亮的图表写了pretty good blog post about this。
鉴于此,很明显你不应该以这种方式改变实体。它表明你所调用的'进程'并不是将它作为一个实体传递给它的对象。认为仅仅因为您当前的对象似乎在不同的上下文中提供您需要的所有内容,在该上下文中使用它是一个好主意,而不是检查该对象是否代表相同的概念,这是一个常见的错误。在这种情况下,您的流程不需要实体。
您应该创建某种值对象以传递给process方法。创建值对象可以像将实体与持久化上下文分离一样简单:Spring JpaRepository - Detach and Attach entity
或者,您可以只计算字段的值,并将其作为第二个参数传递给eService上的流程方法。您的最终选择是按原样发送实体,并让接收服务计算“清晰”的表示。
但是,您还有其他问题。如果你转换到spring-data-jpa而不是编写自己的存储库类,你可能会意识到它们是什么。简单来说,存储库类旨在存储和检索某种持久存储中的实体,它不打算调用其他服务。这将是首先使用repo的任何代码的责任。在spring-mvc应用程序中,这通常是一个控制器,用于协调您的应用程序服务。
说白了,你不应该在repo类中调用服务。除了实体上的CRUD之外什么都不做 - 绝对没有别的。如果您需要它来做其他事情,那么您可能做错了其他事情,或者您为应用选择了错误的架构。
你应该看一下spring-data-jpa。默认情况下,它配置为在运行时自动生成repo的实现。这可能会减少您的代码库,节省您的时间和缺陷,并且不会轻易地让您犯这类架构错误。而且,最重要的是,它比你现在所做的要容易得多。