如何为回调函数正确应用@Transactional注解?
问题场景代码
PersonService
@Service class PersonService( private val personRepository: PersonRepository, private val schedulerService: SchedulerService, ) { fun someMethod() { schedulerService.scheduleTask(this::someTransactionalMethod) } @Transactional fun someTransactionalMethod(int: Int) { personRepository.streamAllBy().forEach { println(it) } } }
SchedulerService
@Service class SchedulerService { fun scheduleTask(task: Task) { task.execute(5) } }
Task函数式接口
fun interface Task { fun execute(int: Int) }
抛出异常
org.springframework.dao.InvalidDataAccessApiUsageException: You're trying to execute a streaming query method without a surrounding transaction that keeps the connection open so that the Stream can actually be consumed; Make sure the code consuming the stream uses @Transactional or any other way of declaring a (read-only) transaction
疑问
调用PersonService.someMethod()时触发上述异常,原以为调用方(SchedulerService)和被调用方法分属不同Bean,事务代理应该生效,但实际失效。将someTransactionalMethod移至第三个Service,通过thirdService::someTransactionalMethod传入时代理正常工作。
原因分析
Spring的事务代理基于Bean的代理实例实现,默认使用JDK动态代理(针对接口)或CGLIB代理(针对类)。当你通过this::someTransactionalMethod传递方法引用时,这里的this是PersonService的原始对象(而非Spring生成的代理对象)——Bean内部引用自身方法时,本质是直接调用原始类逻辑,不会经过代理层,因此@Transactional注解不会被Spring事务拦截器处理,自然无法开启事务。
而将方法移至第三个Service后,thirdService是Spring容器管理的代理对象,方法引用指向代理类的方法,调用时会经过事务拦截器,从而正确触发事务逻辑。
解决方案
方案1:注入自身代理实例
在PersonService中注入自身的代理对象,通过代理对象获取方法引用:
@Service class PersonService( private val personRepository: PersonRepository, private val schedulerService: SchedulerService, // 注入自身代理实例 private val self: PersonService ) { fun someMethod() { // 使用代理对象的方法引用 schedulerService.scheduleTask(self::someTransactionalMethod) } @Transactional fun someTransactionalMethod(int: Int) { personRepository.streamAllBy().forEach { println(it) } } }
Spring会自动注入代理实例,无需额外配置(需确保代理模式正常生效)。
方案2:手动获取代理对象
通过AopContext.currentProxy()获取当前代理对象,需先开启代理暴露:
- 在启动类/配置类添加注解:
@SpringBootApplication @EnableAspectJAutoProxy(exposeProxy = true) class YourApplication fun main(args: Array<String>) { runApplication<YourApplication>(*args) }
- 修改PersonService逻辑:
@Service class PersonService( private val personRepository: PersonRepository, private val schedulerService: SchedulerService, ) { fun someMethod() { val proxy = AopContext.currentProxy() as PersonService schedulerService.scheduleTask(proxy::someTransactionalMethod) } @Transactional fun someTransactionalMethod(int: Int) { personRepository.streamAllBy().forEach { println(it) } } }
方案3:抽离事务方法到独立Bean
将带@Transactional的方法放到单独的Service类中,通过该Bean的方法引用传递,确保调用经过代理层(即你已验证有效的方式)。
关键总结
Spring事务生效的核心是必须通过代理对象调用被@Transactional标记的方法。Bean内部直接引用自身方法(包括方法引用、this调用)会绕过代理,导致事务失效,必须确保方法引用指向代理对象的方法,才能触发事务拦截逻辑。
内容的提问来源于stack exchange,提问作者szmozes

