首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Spring @Transactional on suspend函数

Spring @Transactional on suspend函数
EN

Stack Overflow用户
提问于 2021-07-30 11:27:20
回答 1查看 977关注 0票数 3

我现在有点沮丧,因为我认为这会容易得多,这个问题会更好地记录下来,但我就是找不到解决方案。因此,我在这里寻求帮助。

我正在做一个Kotlin项目,它利用了spring boot版本2.5.3,并使用spring data jpa进行数据库访问和模式定义。这很常见,也很直接。现在假设我们有某种类型的UserService,其中包含一个方法updateUsername,该方法获取一个username作为参数,并在外部服务验证用户名的有效性后更新用户名。为了演示这个问题,我想强调一下,在验证用户名之前,我们手动将用户名设置为"foo"。整个工作单元应该发生在一个事务中,这就是该方法使用@Transactional注释的原因。但是由于对外部服务的调用,该方法将在我们等待http响应时挂起(请注意两个方法上的suspend关键字)。

代码语言:javascript
复制
@Service
class UserService(private val userRepository: UserRepository) {
    @Transactional
    suspend fun setUsername(id: UUID, username: String): Person {
        logger.info { "Updating username..." }
        val user = userRepository.findByIdentityId(identityId = id)
            ?: throw IllegalArgumentException("User does not exist!")

        // we update the username here but the change will be overridden after the verification to the actual username!
        user.userName = "foo"

        verifyUsername(username)

        user.userName = username
        return userRepository.save(user)
    }
    
    private suspend fun verifyUsername(username: String) {
        // assume we are doing some kind of network call here which will suspend while waiting got a response
        logger.info { "Verifying..." }
        delay(1000)
        logger.info { "Finished verifying!" }
    }
}

这段代码编译成功,我也可以执行该方法并启动一个新事务,但是一旦我们在调用delay(1000)时暂停对verifyUsername方法的调用,该事务就会被提交。因此,我们的数据库将实际保持值"foo“作为用户名,直到它被覆盖。但是如果verifyUsername之后的代码失败并抛出异常,我们将无法回滚此更改,因为事务已经提交,foo将永远留在数据库中!这绝对不是预期的行为,因为我们只想在方法的最后提交事务,所以如果出现问题,我们可以在任何时候回滚事务。在这里您可以看到日志:

代码语言:javascript
复制
DEBUG o.s.orm.jpa.JpaTransactionManager - Creating new transaction with name [x.x.x.UserService.setUsername]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
DEBUG o.s.orm.jpa.JpaTransactionManager - Opened new EntityManager [SessionImpl(1406394125<open>)] for JPA transaction
DEBUG o.s.orm.jpa.JpaTransactionManager - Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@9ec4d42]
INFO  x.x.x.UserService - Updating username...
DEBUG org.hibernate.SQL - select person0_.id as id1_6_, person0_.email as email2_6_, person0_.family_name as family_n3_6_, person0_.given_name as given_na4_6_, person0_.identity_id as identity5_6_, person0_.user_name as user_nam6_6_ from person person0_ where person0_.identity_id=?
INFO  x.x.x.UserService - Verifying...
DEBUG o.s.orm.jpa.JpaTransactionManager - Initiating transaction commit
DEBUG o.s.orm.jpa.JpaTransactionManager - Committing JPA transaction on EntityManager [SessionImpl(1406394125<open>)]
DEBUG org.hibernate.SQL - update person set email=?, family_name=?, given_name=?, identity_id=?, user_name=? where id=?
DEBUG o.s.orm.jpa.JpaTransactionManager - Closing JPA EntityManager [SessionImpl(1406394125<open>)] after transaction
INFO  x.x.x.UserService - Finished verifying
DEBUG o.s.orm.jpa.JpaTransactionManager - Creating new transaction with name [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
DEBUG o.s.orm.jpa.JpaTransactionManager - Opened new EntityManager [SessionImpl(319912425<open>)] for JPA transaction
DEBUG o.s.orm.jpa.JpaTransactionManager - Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@1dd261da]
DEBUG org.hibernate.SQL - select person0_.id as id1_6_0_, person0_.email as email2_6_0_, person0_.family_name as family_n3_6_0_, person0_.given_name as given_na4_6_0_, person0_.identity_id as identity5_6_0_, person0_.user_name as user_nam6_6_0_ from person person0_ where person0_.id=?
DEBUG o.s.orm.jpa.JpaTransactionManager - Initiating transaction commit
DEBUG o.s.orm.jpa.JpaTransactionManager - Committing JPA transaction on EntityManager [SessionImpl(319912425<open>)]
DEBUG org.hibernate.SQL - update person set email=?, family_name=?, given_name=?, identity_id=?, user_name=? where id=?
DEBUG o.s.orm.jpa.JpaTransactionManager - Closing JPA EntityManager [SessionImpl(319912425<open>)] after transaction

在这个spring article中,它说“协程上的事务是通过Spring Framework5.2提供的反应式事务管理的编程变体来支持的。对于挂起的功能,提供了一个TransactionalOperator.executeAndAwait扩展。”

这是否意味着@Transactional不能用于挂起方法,而您应该以编程方式处理事务管理?更新:This thread声明@Transactional应该适用于挂起功能。

我还意识到,我的所有数据库操作都是同步运行的,我可以使用r2dbc使它们异步运行(只要我的数据库提供实现规范的驱动程序),但我认为我的问题与我如何与数据库通信无关,而更多的是关于spring如何使用带@Transactional注释的方法处理挂起的调用。

你在这里有什么想法和建议?我绝对不是第一个在Kotlin中使用事务方法做挂起工作的开发人员,我仍然无法找到关于这个问题的有用资源。

谢谢你们!

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2021-07-30 12:15:49

协程事务不支持JPA,因为JPA是完全同步的。协程事务传播仅适用于提供反应式集成的技术,如MongoDB、R2DBC或Neo4j。

JPA采用命令式编程模型,因此其事务管理器将事务状态存储在ThreadLocal存储中。反应式集成,特别是协程,使用协程上下文/反应器的订阅上下文来跟踪事务状态。在ThreadLocal和协程/项目反应器的上下文功能之间没有任何联系。

展望未来,使用诸如JPA之类的阻塞集成需要在协程上下文中特别注意,并且它们的事务作用域需要限制在单个线程上。

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/68590209

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档