【问题标题】:Rolling back transactions programatically in Grails在 Grails 中以编程方式回滚事务
【发布时间】:2013-06-05 18:47:10
【问题描述】:

我有以下代码代码,其中我尝试保存用户,然后发送“单击此链接以确认您的帐户”电子邮件

User.withTransaction { TransactionStatus status ->

    try {
        Role userRole = Role.createCriteria().get {
            eq 'authority', 'USER_ROLE'
        }

        user = user.save(failOnError: true)
        UserRole.create user, userRole

        // sends an email using the Grails mail plugin
        sendRegistrationEmail(username, randomPassword)
        redirect action: 'listUsers'

    } catch (ex) {
        status.rollbackOnly
    }
}

我在事务中发送了电子邮件,因为如果在发送电子邮件期间引发异常,我希望回滚事务。但是我对此进行了测试(通过故意提供无效的邮件服务器凭据)并且在引发异常时事务不会回滚。

如果我替换:

sendRegistrationEmail(username, randomPassword)

与:

throw new Exception()

那么事务确实会回滚,所以我假设这是因为发送邮件时发生的异常是在不同的线程中,并且在引发此异常时事务已经提交,这是正确的吗?

有没有一种方法可以事务性地保存用户并发送电子邮件,即保证只有在成功发送电子邮件时才能保存用户?

更新

我应该解释为什么我不想在未发送电子邮件的情况下保存用户。原因是该电子邮件包含“确认您的帐户”链接,因此如果未发送此电子邮件,他们将无法完成注册过程,也无法再次尝试注册(如果用户已保存) 因为用户名有唯一的约束。

【问题讨论】:

  • 你是如何发送邮件的?它是同步的还是异步的。
  • 此代码在控制器中?而sendRegistrationEmail 正在服务中还是控制器方法?一个关键是控制器不是事务性的,但服务是。如果您将两者混合使用,您可能会遇到问题。
  • @SérgioMichels 代码包含在 User.withTransaction{} 中,因此即使它在控制器中也是事务性的
  • @user2264997 邮件同步发送
  • @Don 是的,我说的是默认值,没有使用 withTransaction。即使您将控制器操作设置为事务性的,放置此逻辑的更好位置是在服务中。

标签: grails transactions grails-orm


【解决方案1】:

回答这个问题:- 有没有办法可以事务性地保存用户并发送电子邮件,即保证只有在成功发送电子邮件时才保存用户?

正如@Sergio Michels 所提到的,是的,有一种更有效的方式来使用服务类,如下所示:-

你应该必须throw一个异常退出事务才能自动回滚事务。我通常在service 层执行此操作,默认情况下是事务性的。我必须将异常从服务层抛出到控制器或任何端点。

例如:

def someServiceMethod{
        Role userRole = Role.createCriteria().get {
            eq 'authority', 'USER_ROLE'
        }

        user = user.save(failOnError: true)
        UserRole.create user, userRole

        // sends an email using the Grails mail plugin
        sendRegistrationEmail(username, randomPassword)

        //Rethrown out to controller automatically, 
        //which results in roll back of transaction
}

def sendRegistrationEmail(username, randomPassword){
    throw new Error() //will rollback the transaction
}

在您的情况下,请确保您没有在 sendRegistrationEmail 中捕获异常。如果是,则必须重新向调用者抛出。

PS:- 我有相同的实现,但是如果发送邮件有任何问题,我会吃异常,而不是回滚,因为问题是邮件服务而不是我的用户保存服务,为什么用户应该遭受。 :-)

更新:-
如果您在控制器中有代码并使用withTransaction,请谈论您的方法。在您的方法中,如果您在其他任何地方使用 Hibernate 会话来保留除了您的问题中的逻辑之外的一些其他数据,那么您需要清除 catch 块中的会话,否则用户数据将获得 flushed 到数据库在后续操作中。

catch(ex){
  object.discard()
  status.rollbackOnly()
}

第二次尝试
您可以尝试包装您旋转的新线程 withTransaction 并查看是否所有事务都回滚。

推断:-
不将事务处理为样板代码,在服务中处理事务decleratively 比在其他任何地方以编程方式处理更方便。

【讨论】:

  • 我已经更新了我的问题来回答你的 PS。我不应该抛出异常来回滚事务,因为在我的catch 块中我调用status.rollbackOnly,它也应该回滚事务。
  • 你的例子和我的不同之处在于一切都发生在同一个线程中。如果您将sendRegistrationEmail 的实现更改为Thread.start { Thread.sleep(3000); throw new Exception() },我想您会看到我遇到的同样问题。
  • 此外,默认情况下,事务不会因Exception 等已检查异常而回滚,仅适用于未检查异常(RuntimeException 的子类)
  • 同意,我有一点很仓促。实际上,我在我的项目中投入了业务 Error,而不是 Exception
猜你喜欢
  • 1970-01-01
  • 2014-03-15
  • 2018-11-17
  • 2015-10-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-11-21
相关资源
最近更新 更多