为何JpaRepository在普通线程与异步线程返回的实体状态不同
问题1:造成上述差异的底层原因是什么?
核心原因是Spring Boot Web项目默认开启的OSIV(Open Session In View)机制,对应JPA规范的实现是OpenEntityManagerInViewInterceptor或OpenEntityManagerInViewFilter:
- 同步Web请求进入主线程时,OSIV组件会提前创建一个
EntityManager实例绑定到当前线程,整个请求生命周期内所有JPA操作都会复用这个实例,因此JpaRepository查询返回的实体会一直被这个EntityManager托管,contains方法返回true。 - 被
@Async注解修饰的方法、自定义线程池提交的异步任务,都运行在独立的新线程中,新线程没有绑定OSIV创建的EntityManager。此时每次调用JpaRepository方法,Spring都会为这次调用单独创建临时的EntityManager和事务,方法执行完成后事务提交、EntityManager立即关闭,返回的实体自然处于DETACHED状态,contains方法返回false。
问题2:updateAuthor()方法中的book实体存储在哪个持久化上下文中?该持久化上下文的初始化、关闭生命周期是怎样的?
updateAuthor方法中的book实体归属于OSIV为当前Web请求主线程创建的EntityManager对应的持久化上下文,生命周期和当前Web请求完全绑定:
- 初始化:Web请求到达服务端时,OSIV拦截器/过滤器触发,创建新的
EntityManager实例,绑定到当前处理请求的线程上。 - 存活期:整个请求处理过程中,所有JPA操作(包括
JpaRepository调用、手动EntityManager操作、事务方法内的JPA操作)都会复用这个绑定的EntityManager,所有查询返回的实体都会被该持久化上下文托管。 - 销毁:请求处理完成、响应结果返回给客户端后,OSIV组件会主动关闭绑定的
EntityManager,持久化上下文被清除,所有托管实体转为DETACHED状态。
问题3:我此前认为事务关闭后,持久化上下文中的所有实体会转为DETACH状态,这个结论是否仍然正确?Spring Data JPA是否存在特殊处理?
这个结论完全符合JPA规范,仍然正确。Spring Data JPA本身没有做任何违反JPA规范的特殊处理,你观察到的事务提交后实体仍然处于PERSISTED状态的现象,是Spring Web层的OSIV机制导致的,和Spring Data JPA无关。
补充说明你观察到的更新生效逻辑:updateService.updateAuthorTransactional方法加了@Transactional注解,执行时会复用当前线程绑定的EntityManager开启事务,事务提交时会自动flush持久化上下文中的实体变更到数据库;但因为事务提交后OSIV绑定的EntityManager仍然存活,所以实体仍然处于托管状态,不会转为DETACHED。
而异步场景下,查询返回的实体已经是DETACHED状态,传入事务方法时,当前事务对应的EntityManager中没有该实体的托管实例,因此你对实体的修改不会被自动同步到数据库,除非显式调用EntityManager.merge()方法合并脱管实体的变更。
内容的提问来源于stack exchange,提问作者Huy Ha

