Hibernate并发访问管理疑问:为何线程可获取已锁定数据库记录?
核心问题:为何select ... for update没锁住记录?
你遇到的问题本质是事务或锁机制未按预期生效,以下是几个最可能的原因及排查方向:
1. REQUIRES_NEW事务传播属性未生效
Spring的事务注解基于动态代理实现,如果getRecordCondition方法是被同一个类内部的其他方法调用,而非外部通过代理类调用,@Transactional(propagation = Propagation.REQUIRES_NEW)会完全失效。这种情况下,两个线程可能共用同一个事务(或根本没开启新事务),导致select ... for update的锁提前释放。
排查方式:
- 在
getRecordCondition方法的开头和结尾打印日志,输出TransactionSynchronizationManager.getCurrentTransactionName()和TransactionSynchronizationManager.isActualTransactionActive(),确认每次调用都开启了独立的新事务。 - 确保调用
getRecordCondition的代码是通过Spring代理对象调用,而非类内部直接调用。
2. Session管理异常
你提到DAO用getCurrentSession()获取Session,但需确认Session的绑定规则:
- Spring整合Hibernate时,默认
getCurrentSession()返回的Session与当前线程的事务绑定(通过ThreadLocal实现),正常情况下不同线程会有独立的Session。但如果hibernate.current_session_context_class配置错误(比如未设置为org.springframework.orm.hibernate5.SpringSessionContext),可能导致Session未正确绑定到线程,甚至出现多线程共用Session的情况。 - 检查
findRecord方法是否手动关闭了Session:如果在try块里加了session.close(),会直接终止当前事务,导致select ... for update的锁被释放,线程2就能立即获取记录。Spring管理的Session不需要手动关闭,事务结束后会自动回收。
排查方式:
- 在
findRecord和updateRecordStatus方法中打印Session的hashCode(),确认同一个事务内的两个DAO方法使用的是同一个Session。 - 检查Spring配置文件中Hibernate的
current_session_context_class配置项,确保值为org.springframework.orm.hibernate5.SpringSessionContext。
3. select ... for update的锁定时机问题
LockMode.UPGRADE对应的select ... for update锁,只有在当前事务提交或回滚后才会释放。如果findRecord和updateRecordStatus不在同一个事务内,findRecord的事务结束后锁就会释放,线程2就能立即查询到记录。
比如:如果updateRecordStatus方法自己加了@Transactional注解,且传播属性不是REQUIRED,就会开启新事务,导致两个DAO方法分属不同事务,锁提前释放。
Connection与Session的区别
- Connection:JDBC层面的物理连接,是应用与数据库之间的通信通道,直接用于执行SQL语句、提交/回滚事务。一个Connection同一时间只能被一个线程使用。
- Session:Hibernate(ORM框架)的会话对象,封装了Connection,提供对象缓存、持久化操作(如save/update)的接口。一个Session通常对应一个Connection,在事务期间会持有Connection,直到事务结束。简单说,Session是ORM对JDBC Connection的封装,让开发者可以用对象操作代替直接写SQL。
多线程是否会共用同一个Session?
正常情况下不会:
Spring整合Hibernate时,Session通过ThreadLocal绑定到当前线程的事务,每个线程的独立事务对应一个独立的Session。只有当配置错误(比如Session被错误注册为单例Bean)、或者手动创建Session并在多线程间共享时,才会出现多线程共用Session的情况,这会导致严重的线程安全问题(比如Connection被多线程同时使用)。
修复建议
- 确保
@Transactional(propagation = Propagation.REQUIRES_NEW)生效:通过代理对象调用getRecordCondition方法,避免内部调用。 - 检查Session配置:确认
hibernate.current_session_context_class设置正确,且DAO方法中没有手动关闭Session。 - 确保
findRecord和updateRecordStatus在同一个事务内:去掉DAO方法上的事务注解(如果有的话),让它们复用服务层的事务。 - 验证数据库隔离级别:确保数据库的隔离级别不是
READ_UNCOMMITTED(默认是READ_COMMITTED,足够支持select ... for update锁)。
内容的提问来源于stack exchange,提问作者SeanM

