在async(Dispatchers.Default)协程调用Repository与主线程结果不一致问题
问题分析与解决方案
核心问题原因
- Spring事务的线程绑定特性:
@Transactional注解的事务上下文默认与当前执行线程绑定。主线程中保存Family后,事务尚未提交,而async(Dispatchers.Default)会将创建子项的任务提交到线程池的其他线程执行——这些线程不在同一个事务上下文中,无法读取主线程事务中未提交的Family数据。 - 数据库隔离级别限制:默认的
读已提交隔离级别下,其他线程无法看到未提交的事务数据,因此ChildService中查询不到刚创建的Family,插入Child时触发外键约束校验失败。
解决方案
方案1:取消跨线程协程(最简有效)
将创建子项的逻辑放在当前事务线程中执行,避免跨线程导致的事务上下文丢失:
@Service class FamilyService( private val familyRepository: FamilyRepository, private val childService: ChildService ) { @Transactional(rollbackOn = [Exception::class]) fun createFamily(familyLastName: String, childrenNames: List<String>): Family { val family = createFamilyEntity(familyLastName) println("family (in main): ${familyRepository.findByIdOrNull(family.id) ?: "none"}") // 直接在当前事务线程中创建子项,不切换线程 val children = childrenNames.map { name -> childService.createChild(family.id, name) } return family } private fun createFamilyEntity(familyLastName: String): Family { val family = Family(id = UUID.randomUUID(), familyLastName = familyLastName) return familyRepository.save(family) } }
方案2:使用Spring协程事务支持(需协程场景)
如果必须保留协程异步执行逻辑,需利用Spring对Kotlin协程的事务上下文绑定支持,确保协程共享同一事务:
@Service class FamilyService( private val familyRepository: FamilyRepository, private val childService: ChildService ) { // 改为suspend函数,Spring自动管理协程事务上下文 @Transactional(rollbackOn = [Exception::class]) suspend fun createFamily(familyLastName: String, childrenNames: List<String>): Family { val family = createFamilyEntity(familyLastName) println("family (in main): ${familyRepository.findByIdOrNull(family.id) ?: "none"}") // 使用coroutineScope确保协程在同一事务上下文内执行 val children = coroutineScope { childrenNames.map { name -> async { childService.createChild(family.id, name) } } }.awaitAll() return family } private fun createFamilyEntity(familyLastName: String): Family { val family = Family(id = UUID.randomUUID(), familyLastName = familyLastName) return familyRepository.save(family) } } // ChildService对应改为suspend函数 @Service class ChildService( private val childRepository: ChildRepository, private val familyRepository: FamilyRepository ) { suspend fun createChild(familyId: UUID, name: String): Child { println("family (in child): ${familyRepository.findByIdOrNull(familyId) ?: "none"}") val child = Child(id = UUID.randomUUID(), familyId = familyId, name = name) return childRepository.save(child) } }
注意事项
- 若使用方案2,需确保项目引入
spring-boot-starter-webflux或其他支持协程事务的Spring Starter。 - 避免在事务协程中手动指定
Dispatchers.Default,优先使用Spring管理的协程上下文,防止事务上下文丢失。
内容的提问来源于stack exchange,提问作者akzxtuallyyy
相关产品推荐
相关产品推荐

