Grails 4与Spock集成测试并发幂等性失败 Hibernate会话关联异常问题
报错根因
你遇到的org.hibernate.HibernateException: Illegal attempt to associate a collection with two open sessions报错,是Grails 2.x到Grails 4.x的session管理机制变化导致的:
Grails 2.x测试默认会将session绑定到所有衍生线程,而Grails 4.x基于Spring事务上下文,Hibernate Session默认是线程隔离的。你在主线程加载的Thing实例以及关联的KiindUser.paymentInfoByCurrency集合,都绑定了主线程的测试Session,子线程拿到这个关联了其他Session的实体后,哪怕只是读取集合触发懒加载,也会触发Session校验报错。
问题1:Grails 4/Spock 并发测试最佳实践
集成测试验证并发逻辑是完全合理的,尤其是幂等性、并发锁这类依赖真实数据库事务、锁机制的场景,单元测试无法覆盖对应的边界情况,不属于不推荐的实践。通用的最佳实践如下:
- 不要跨线程传递领域实体:子线程需要用到的实体,一律在子线程内部通过ID查询,禁止传递主线程加载的实体实例,避免跨Session共享实体的问题
- 明确子线程事务边界:如果待测试的服务方法没有加
@Transactional注解,需要主动在子线程逻辑外层声明独立事务,确保操作可以正常提交到数据库 - 禁用测试类自动回滚:大部分Grails集成测试默认开启自动事务回滚,这种配置下主线程的事务隔离级别会挡住子线程提交的数据,并发测试建议标注
@Rollback(false),测试结束后手动清理测试数据 - 隔离测试数据:每次测试前单独插入本次用的测试数据,不要复用其他测试的存量数据,避免并发测试之间互相干扰
问题2:跨线程Session处理方案
不建议将测试主线程的Session绑定到子线程共享:Hibernate Session本身设计为非线程安全,多线程共享会导致脏读、数据状态错乱等不可预期的问题,风险极高。
推荐采用子线程独立Session的方案,修改后测试代码可以正常读取到子线程提交的所有数据,示例代码如下:
// 测试类顶部标注禁用自动回滚 @Rollback(false) class YourServiceIntegrationSpec extends IntegrationSpec { @Autowired TransactionTemplate transactionTemplate void testConcurrentIdempotency() { given: // 只获取待操作实体的ID,不传递主线程加载的实体实例 Long testThingId = Thing.findAll(someId)[0].id int attemptsPerThing = 5 when: def pool = Executors.newFixedThreadPool(attemptsPerThing) List<Future> futureList = [] for (int i = 0; i < attemptsPerThing; i++) { Future future = pool.submit({ // 子线程开启独立事务,使用当前线程的独立Session transactionTemplate.execute { status -> try { // 子线程内部查询所需实体,绑定当前线程的Session Thing thing = Thing.get(testThingId) return serviceToBeTested.functionToBeTestedThatCommitsThingsToDB(thing) } catch (Exception e) { log.error("服务调用异常", e) return null } } }) futureList.add(future) } futureList.each { future -> def futureResults = future.get() assert futureResults == null } pool.shutdown() assert pool.awaitTermination(60, TimeUnit.SECONDS) // 清理主线程Session缓存,避免读到缓存的旧数据 sessionFactory.currentSession.clear() then: // 此时查询的是数据库最新提交的状态,可正常校验幂等性 Thing finalThing = Thing.get(testThingId) // 后续幂等性校验断言 } }
如果你的待测试服务方法本身已经标注了@Transactional,可以不用额外加TransactionTemplate,只要保证子线程内部查询实体即可。
内容的提问来源于stack exchange,提问作者Tyrel H
相关产品推荐
相关产品推荐

