多线程环境下自定义EntityManager的问题排查与求解
1. 单例EntityManager引发线程安全异常
将EntityManager定义为单例Bean并注入Service后,多用户并发请求时抛出The statement has been closed,核心原因是EntityManager本身是会话级对象,不具备线程安全性。
单例Bean意味着整个应用中只有一个EntityManager实例,多个线程共用该实例时会出现状态冲突:比如线程A执行完查询并关闭了statement,线程B复用同一个EntityManager执行查询时,就会访问到已关闭的资源,触发异常。
对应代码中,手动创建的EntityManager被Spring作为单例管理,完全违背了JPA中EntityManager的设计初衷——每个线程/事务应拥有独立实例。
2. 原型Scope导致数据库连接泄漏
将EntityManager改为原型Bean后,每次注入都会创建新实例,但出现连接泄漏,原因是Spring不会自动管理原型Bean的生命周期。
EntityManager由EntityManagerFactory.createEntityManager()创建后,必须手动调用close()方法释放底层数据库连接(归还到HikariCP连接池)。原型模式下,每次请求都会生成新的EntityManager,但如果没有显式关闭,连接池中的连接会被持续占用,最终耗尽所有可用连接,导致泄漏问题。
3. @PersistenceContext注入后ClassCastException异常
使用@PersistenceContext注入EntityManager时,抛出jdk.proxy2.$Proxy288 cannot be cast to org.hibernate.query.sql.internal.NativeQueryImpl,原因是此时注入的是Spring生成的事务感知代理,而非Hibernate原生的EntityManager实例。
在没有@Transactional注解的情况下,Spring代理的EntityManager并不会直接创建实际的Hibernate Session,调用createNativeQuery()返回的Query对象也是代理包装后的实例。当尝试unwrap(NativeQueryImpl.class)时,代理无法直接转换为Hibernate的具体实现类,从而触发类型转换异常。
4. @Transactional注解解决所有问题的原因
在Service类上添加@Transactional后问题解决,这是因为Spring的事务管理机制会自动处理以下关键逻辑:
- 线程安全的EntityManager实例:事务开启时,Spring会为当前线程创建独立的EntityManager实例并绑定到线程上下文;事务结束后自动销毁实例,避免多线程冲突。
- 自动管理连接生命周期:事务结束时,Spring会自动调用EntityManager的
close()方法,将底层数据库连接归还到HikariCP连接池,彻底解决连接泄漏问题。 - 支持Hibernate原生API调用:事务范围内,代理的EntityManager会直接关联到实际的Hibernate Session,此时调用
createNativeQuery()返回的Query可以成功unwrap()为NativeQueryImpl,执行结果转换操作。
环境说明
SpringBoot 3.1.11,hibernate-core: 6.2.24.Final,HikariCp: 5.0.1
内容的提问来源于stack exchange,提问作者Adrian ZOU -SHANGHAI PRODUCTIO

