【问题标题】:Grails Service TransactionsGrails 服务事务
【发布时间】:2019-01-23 12:56:47
【问题描述】:

我试图让事务在 Grails 服务中运行,但我没有得到我期望的结果。如果我的假设不正确,有人可以告诉我我做错了什么吗?

我的域类:

class Account {

  static constraints = {
    balance(min: 0.00)
  }

  String companyName
  BigDecimal balance = 0.00
  Boolean active = true

  String toString() {
    return "${companyName} : ${balance}"
  }
}

我的服务:

class AccountService {

  static transactional = true

  def transfer(Account source, Account destination, amount) throws RuntimeException {

    if (source.active && destination.active) {
      source.balance -= amount

      if (!source.save(flush: true)) {
        throw new RuntimeException("Could not save source account.")
      } else {
        destination.balance += amount

        if (!destination.save(flush: true)) {
          throw new RuntimeException("Could not save destination account.")
        }
      }
    } else {
      throw new RuntimeException("Both accounts must be active.")
    }
  }

  def someMethod(Account account) throws RuntimeException {

    account.balance = -10.00

    println "validated: ${account.validate()}"

    if(!account.validate()) {
      throw new RuntimeException("Rollback!")
    }
  }
}

我的单元测试: 导入 grails.test.*

class AccountServiceTests extends GrailsUnitTestCase {

  def AccountService

  protected void setUp() {
    super.setUp()
    mockDomain(Account)
    AccountService = new AccountService()
  }

  protected void tearDown() {
    super.tearDown()
  }

  void testTransactional() {
    def account = new Account(companyName: "ACME Toy Company", balance: 2000.00, active: true)

    def exception = null

    try {
      AccountService.someMethod(account)
    } catch (RuntimeException e) {
      exception = e
    }

    assert exception instanceof RuntimeException

    println "exception thrown: ${exception.getMessage()}"

    assertEquals 2000.00, account.balance
  }
}

结果:

Testsuite: AccountServiceTests
Tests run: 1, Failures: 1, Errors: 0, Time elapsed: 1.068 sec
------------- Standard Output ---------------
--Output from testTransactional--
validated: false
exception thrown: Rollback!
------------- ---------------- ---------------
------------- Standard Error -----------------
--Output from testTransactional--
------------- ---------------- ---------------

Testcase: testTransactional took 1.066 sec
    FAILED
expected:<2000.00> but was:<-10.00>
junit.framework.AssertionFailedError: expected:<2000.00> but was:<-10.00>
    at AccountServiceTests.testTransactional(AccountServiceTests.groovy:89)
    at _GrailsTest_groovy$_run_closure4.doCall(_GrailsTest_groovy:203)
    at _GrailsTest_groovy$_run_closure4.call(_GrailsTest_groovy)
    at _GrailsTest_groovy$_run_closure2.doCall(_GrailsTest_groovy:147)
    at _GrailsTest_groovy$_run_closure1_closure19.doCall(_GrailsTest_groovy:113)
    at _GrailsTest_groovy$_run_closure1.doCall(_GrailsTest_groovy:96)
    at TestApp$_run_closure1.doCall(TestApp.groovy:66)
    at gant.Gant$_dispatch_closure4.doCall(Gant.groovy:324)
    at gant.Gant$_dispatch_closure6.doCall(Gant.groovy:334)
    at gant.Gant$_dispatch_closure6.doCall(Gant.groovy)
    at gant.Gant.withBuildListeners(Gant.groovy:344)
    at gant.Gant.this$2$withBuildListeners(Gant.groovy)
    at gant.Gant$this$2$withBuildListeners.callCurrent(Unknown Source)
    at gant.Gant.dispatch(Gant.groovy:334)
    at gant.Gant.this$2$dispatch(Gant.groovy)
    at gant.Gant.invokeMethod(Gant.groovy)
    at gant.Gant.processTargets(Gant.groovy:495)
    at gant.Gant.processTargets(Gant.groovy:480)

我的期望:

当账户的余额为负时,它不应该验证(它没有),应该抛出一个 RuntimeException(它是),并且账户应该回滚到它之前的状态(余额:2000) ,这就是它分崩离析的地方。

我在这里错过了什么?

【问题讨论】:

    标签: grails service transactions grails-test


    【解决方案1】:

    单元测试只是 Groovy 或 Java 类,因此没有 Spring 应用程序上下文,因此没有事务支持。您必须为单元测试模拟它,但这不会测试事务性,只是模拟的质量。将此转换为集成测试,不要在服务上调用 new,使用依赖注入:

    class AccountServiceTests extends GroovyTestCase {
    
      def AccountService
    
      void testTransactional() {
        def account = new Account(companyName: "ACME Toy Company", balance: 2000.00,
                                  active: true)
        account.save()
        assertFalse account.hasErrors()
    
        String message = shouldFail(RuntimeException) {
          AccountService.someMethod(account)
        }
    
        println "exception thrown: $message"
    
        assertEquals 2000.00, account.balance
      }
    }
    

    请注意,实际的异常可能是包装器异常,原因是您抛出的异常。

    【讨论】:

    • 嘿,伯特,完全有道理。唉,还是没有运气。它仍然按照应有的方式抛出异常,但它没有回滚任何东西。
    • 现在就是这样。帐户实例的数据不会回滚,但不会保留更改。添加'def sessionFactory'作为依赖注入字段,并在最后一个assertEquals之前添加对'sessionFactory.currentSession.clear()'和'account = Account.get(account.id)'的调用以强制重新加载帐户实例,测试将通过。
    • 只是一个简短的说明,供人们使用它来编写事务服务的集成测试。集成测试本身是事务性的,因此您的被测服务不会出现在新事务中。这可能会导致意外行为。一种解决方法是在集成测试中关闭事务(静态事务 = false)。
    • 另一种解决方法是在您的服务方法中添加以下注释:@Transactional(propagation = org.springframework.transaction.annotation.Propagation.REQUIRES_NEW) 这将允许您在测试类中维护事务,这使 setUp() 和 tearDown() 正常工作。
    • @SteveGoodman 在控制器调用 withTransaction{} 块内的服务的情况下,这不会也创建一个新事务吗?
    【解决方案2】:

    我尝试了代码,但在集成测试中发现了同样的问题。我使用 Grails 1.2

    根据Jira GRAILS-3765,这是一个已知问题并且仍然存在。 (我不知道为什么当 1.1.x 已经发布很长时间时它只说“影响版本 1.0.4”)。

    基于这些点,我认为这是 Grails 中的一个错误。 Jira note 有一个解决方法,但我没有尝试过。但是,根据问题,它在运行应用程序时仍然可以工作;这可以手动确认。

    【讨论】:

      【解决方案3】:

      您运行的是哪个版本的 Grails? v1.1.1 有一个 bug,transactional=true 不能正常工作。

      【讨论】:

      • 是的。尝试取出transactional = true。看看你是否有更好的结果。或者,尝试使用 1.2。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-06-16
      • 1970-01-01
      • 2013-12-29
      • 2014-10-06
      • 1970-01-01
      相关资源
      最近更新 更多