JPA中读操作是否无需事务?连接池耗尽问题排查
首先,先拆解你遇到的连接池耗尽问题:你看到的org.hibernate.HibernateException: The internal connection pool has reached its maximum size and no connection is currently available!,本质是代码没有正确释放数据库连接,根源出在你手动管理事务的方式上。
你的代码哪里出问题了?
看你的XxspAsnOutHeaderRepo实现,每次创建Repo实例都会新建EntityManager和EntityTransaction,但在getOneOutHeaderWithFlag方法里:
- 你只在事务被标记为回滚时执行了
rollback(),但正常完成读操作后,既没有提交事务,也没有关闭EntityManager - 当你循环创建30个Repo实例时,每个实例的EntityManager都死死占着连接池的连接,直到20个连接全被用完,自然就触发报错了。
核心问题:JPA读操作必须用事务吗?
答案是:不一定,得看你的业务场景。
不需要显式事务的场景(你的情况就属于这个)
对于单纯的只读查询,JPA(底层Hibernate)会自动切换到自动提交模式——就像JDBC的auto-commit一样,每次查询会自动获取连接、执行SQL、然后立刻释放连接,不会占用资源。这就是为什么你删掉if (!transaction.isActive()) transaction.begin();后,30次查询都能跑通,而且性能还提升了:因为连接被及时回收了。
需要显式事务的场景
当然,不是所有读操作都能不用事务,以下情况必须显式开启:
- 你需要多个读操作的一致性:比如在同一个事务中连续查两次数据,确保中间不会被其他事务修改(避免脏读)
- 读操作之后还要执行写操作:必须把读写放在同一个事务里,保证原子性
- 依赖JPA的事务性特性:比如二级缓存的更新、某些批量操作的事务支持
你尝试的两种修改为什么有不同效果?
删掉显式开启事务的代码:
这时候Hibernate自动用auto-commit模式,查询完立刻释放连接,完美解决连接耗尽问题,而且没有事务提交的额外开销,所以性能更好,完全适配你的只读场景。添加
transaction.commit();:
提交事务会触发连接的回收,所以不会耗尽连接池,但事务提交本身会带来额外的开销(比如事务日志写入、连接状态重置),所以性能变慢——对于单纯的只读操作来说,这完全是没必要的开销。
给你的优化建议
针对你的只读场景,推荐用自动提交模式,同时记得用完EntityManager一定要关闭(你之前的代码没关,即使不用事务也会有资源泄漏风险),修改后的代码示例:
public class XxspAsnOutHeaderRepo { public static Logger logger = Logger.getLogger(XxspAsnOutHeaderRepo.class); public List<XxspAsnOutHeaderEntity> getOneOutHeaderWithFlag(String flag) { EntityManager entityManager = null; TypedQuery<XxspAsnOutHeaderEntity> query = null; try { entityManager = JPAUtil.getEntityManagerFactory().createEntityManager(); logger.info("getting headers from Database..... "); query = entityManager.createNamedQuery(XxspAsnOutHeaderEntity.FIND_ALL_HEADERS_FLAG_SIX, XxspAsnOutHeaderEntity.class) .setParameter("proc_flag", flag).setMaxResults(1); return query.getResultList(); } catch (PersistenceException ex) { logger.error("Error occured while fetching data from DataBase...\n", ex); throw ex; } finally { // 关键:用完关闭EntityManager,释放连接池资源 if (entityManager != null && entityManager.isOpen()) { entityManager.close(); } } } }
另外,更优的实践是复用EntityManager(比如通过Spring等框架做依赖注入),而不是每次创建新实例,这样能进一步减少连接池的压力,提升性能。
内容的提问来源于stack exchange,提问作者The Coder

