Spring单请求多事务:前一事务加载的实体是否仍在持久化上下文?
好问题!这个场景的核心其实取决于两个关键点:事务的执行方式和是否开启了Open Session in View(OSIV)模式,咱们分情况拆解:
1. 先明确基础概念
@Transactional注解默认的事务传播行为是REQUIRED,简单说就是如果当前没有事务就新建一个,有事务就加入当前事务。- Hibernate的持久化上下文是和
Session绑定的,实体的托管/游离状态完全由当前Session决定。
2. 场景一:两个方法在同一个Bean内调用(内部方法调用)
如果你的代码是类似这样的:
@Service public class MyService { public void processRequest() { foo(); // 内部调用,不会触发事务代理 bar(); // 同理 } @Transactional public void foo() { // 加载实体 User user = userRepository.findById(1L).get(); } @Transactional public void bar() { // 尝试访问之前的实体? } }
这种情况下,Spring的AOP代理不会对内部方法调用生效——也就是说foo()和bar()上的@Transactional其实都没起作用,整个processRequest()方法如果没有标注事务,那根本没有事务;如果processRequest()标注了事务,那两个方法都在同一个事务里。
不管哪种情况,只要实体是在当前Session(持久化上下文)里加载的,在整个请求的后续步骤中(只要Session没关闭)都能访问。
3. 场景二:两个方法通过代理调用,形成独立事务
如果是通过不同Bean调用,或者注入自身的代理对象来调用(确保@Transactional生效),比如:
@Service public class MyService { @Autowired private MyService selfProxy; // 注入代理对象 public void processRequest() { selfProxy.foo(); // 触发事务,执行完后事务提交 selfProxy.bar(); // 触发新的事务 } @Transactional public void foo() { User user = userRepository.findById(1L).get(); // 事务提交后,这个Session会被关闭吗? } @Transactional public void bar() { // 能访问foo里加载的user吗? } }
这时候就要看是否开启了OSIV:
3.1 开启了Open Session in View(Spring Boot默认开启)
OSIV模式的核心是在整个HTTP请求周期内保持一个Hibernate Session打开,不管中间的事务是否提交或回滚。
foo()的事务提交时,Session不会被关闭,只是事务结束了,持久化上下文依然存在。- 所以
foo()中加载的实体仍然处于托管状态,在bar()里可以正常访问,甚至如果修改实体属性,在bar()的事务提交时会自动同步到数据库。
3.2 未开启OSIV模式
如果关闭了OSIV(比如在Spring Boot中设置spring.jpa.open-in-view=false),那么每个事务会绑定自己的Session:
foo()的事务提交后,对应的Session会被关闭,持久化上下文销毁,加载的实体变成游离状态。- 这时候在
bar()里再访问这个实体,就无法再通过持久化上下文进行懒加载或自动同步了,除非你重新将其合并到bar()的Session中(比如调用entityManager.merge(user))。
4. 你的疑问点解答
foo()的事务已结束,但由于请求未完成,Hibernate Session仍存活,持久化上下文是否依旧存在?
- 如果开启OSIV:Session会在整个请求周期存活,持久化上下文也一直存在,即使单个事务结束。
- 如果未开启OSIV:每个事务对应独立Session,事务结束Session就关闭,持久化上下文随之销毁。
所以结论是:在绝大多数Spring Boot项目(默认开启OSIV)中,foo()加载的实体在bar()里依然可以在持久化上下文中访问;如果关闭了OSIV,那实体会变成游离状态,无法再通过原持久化上下文操作。
内容的提问来源于stack exchange,提问作者Mohammed Feroz

