你有@Transactional
方法或服务类本身的注释?
这可以解释观察到的行为。
当在事务中执行方法时,从数据库获取或合并/保存到数据库的实体将被缓存,直到事务结束(通常是方法结束)。这意味着对具有相同 ID 的实体的任何调用都将直接从缓存返回,并且不会访问数据库。
以下是一些有关 Hibernate 缓存和代理的文章:
- 通过示例了解 Hibernate 一级缓存 https://howtodoinjava.com/hibernate/understanding-hibernate-first-level-cache-with-example/
- JPA 代理如何工作以及如何使用 Hibernate 取消代理 https://vladmihalcea.com/how-does-a-jpa-proxy-work-and-how-to-unproxy-it-with-hibernate/
- 使用 JPA 和 Hibernate 初始化 LAZY 实体和集合代理的最佳方法 https://vladmihalcea.com/initialize-lazy-proxies-collections-jpa-hibernate/
回到你的例子:
- call
findById(id)
首先然后getOne(id)
两者返回相同的实体对象
- call
getOne(id)
首先然后findById(id)
为两者返回相同的代理
那是因为他们有共同点id
并在同一事务中执行。
有关的文档getOne()
指出它could return an instance
而不是引用(HibernateProxy),因此可以期望它返回一个实体:
T getOne(ID id)
返回对具有给定标识符的实体的引用。
根据 JPA 持久性提供程序的实现方式,这很可能是
始终返回一个实例并抛出 EntityNotFoundException
第一次访问。其中一些会立即拒绝无效标识符。
参数:
id - 不能为空。
返回:
对具有给定标识符的实体的引用。
有关的文档findById()
另一方面,没有任何暗示它可以返回任何东西,但Optional
实体或空Optional
:
可选 findById(ID id)
通过 id 检索实体。
参数: id - 不能为空。
返回:具有给定 id 的实体,如果未找到,则为Optional#empty()
我花了一些时间寻找更好的解释,但未能找到,所以我不确定这是否是实现中的一个错误findById()
或者只是一个没有(充分)记录的功能。
作为问题的解决方法,我可以建议:
- 不要以相同的交易方式两次收购同一实体。 :)
- Avoid using
@Transactional
when not need. Transactions can be managed manually too. Here are some good articles on that subject:
- 5 个常见的 Spring @Transactional 陷阱 https://codete.com/blog/5-common-spring-transactional-pitfalls/
- Spring 事务传播模式 https://codete.com/blog/spring-transaction-propagation-modes/
-
Spring 陷阱:事务测试被认为是有害的 https://www.nurkiewicz.com/2011/11/spring-pitfalls-transactional-tests.html.
- 在使用其他方法(重新)加载之前分离第一个加载的实体/代理:
import javax.persistence.EntityManager;
import org.springframework.transaction.annotation.Transactional;
@Transactional
@Service
public class SomeServiceImpl implements SomeService {
private final SomeRepository repository;
private final EntityManager entityManager;
// constructor, autowiring
@Override
public void someMethod(long id) {
SomeEntity getOne = repository.getOne(id); // Proxy -> added to cache
entityManager.detach(getOne); // removes getOne from the cache
SomeEntity findById = repository.findById(id).get(); // Entity from the DB
}
- 与第三种方法类似,但不是从缓存中删除单个对象,而是使用
clear()
method:
import javax.persistence.EntityManager;
import org.springframework.transaction.annotation.Transactional;
@Transactional
@Service
public class SomeServiceImpl implements SomeService {
private final SomeRepository repository;
private final EntityManager entityManager;
// constructor, autowiring
@Override
public void someMethod(long id) {
SomeEntity getOne = repository.getOne(id); // Proxy -> added to cache
entityManager.clear(); // clears the cache
SomeEntity findById = repository.findById(id).get(); // Entity from the DB
}
相关文章:
- 何时使用 getOne 和 findOne 方法 Spring Data JPA https://stackoverflow.com/questions/24482117/when-use-getone-and-findone-methods-spring-data-jpa
- Hibernate 会话:evict() 和 merge() 示例 https://www.concretepage.com/hibernate/hibernate-session-evict-and-merge-example
- Hibernate 中的clear()、evict() 和close() 方法 https://www.connect2java.com/tutorials/hibernate/clear-evict-and-close-methods-in-hibernate/
- JPA - 从持久性上下文中分离实体实例 https://www.logicbig.com/tutorials/java-ee-tutorial/jpa/detaching.html
- Spring Data JPA 中 getOne 和 findById 之间的区别? https://www.javacodemonk.com/difference-between-getone-and-findbyid-in-spring-data-jpa-3a96c3ff
EDIT:
这里有一个简单的项目 https://github.com/Martin-BG/jpa-transactional-oddities展示问题或功能(取决于观点)。