grails 2.3之前
For Example
AccountController.groovy
class AccountController {
def transfer = {
def fromAccount = params?.fromAccount
def toAccount = params?.toAccount
def amountToTransfer = params?.amount
// some validity checks, could be performed through a Command Object class
...
// withdraw from the source account
fromAccount.balance -= amountToTransfer
fromAccount.save(flush:true)
// some logic that raises an error or throws an exception
...
// transfer to the target account
toAccount.balance += amountToTransfer
toAccount.save(flush:true)
}
}
这是一个关于账户的controller,它定义了一个transfer的闭包
然后定义了转出账户,转入账户和转账金额
然后对转出账户的余额进行了减少并立即保存数据库
然后对转入账户的余额进行了增加并立即保存数据库
但是,当程序运行到到‘some logic’模块时发生了错误或者抛出了异常,转出账户的钱转出了但转入账户却没有转入
导致出现这个情况的主要原因是:没有使用事务,数据库没有回滚
Grails Model – Business Logic in Services
Grails paradigm is to avoid putting business code in the Controller layer but rather in the model or service layer.
(Grails范例是避免把业务代码写入controller层而宁可说写在model层或者service层)
TransferService.groovy
class TransferService {
def transfer(fromAccount, toAccount, amount) {
// withdraw from the source account
fromAccount.balance -= amountToTransfer
fromAccount.save(flush:true)
// some logic that raises an error or throws an exception
...
// transfer to the target account
toAccount.balance += amountToTransfer
toAccount.save(flush:true)
}
}
AccountController.groovy
class AccountController {
def transferService
def transfer = {
...
// call service to make the transfer
transferService.transfer(fromAccount, toAccount, amountToTransfer)
}
}
代码基本没变,只是原先写在controller中,现在写在了service中,然后在controller中进行了调用
这时,当你先做了转出操作,然后发生了错误或者异常,第一个账户的钱则不会转出,达到了想要的目的。
这是为什么呢?原因就是:by default every call to a Service’s method is made transactional.
(缺省状况下每个service方法的调用都是事务的。)
但这是怎么办到的呢?
Using a combination of Spring IoC for injecting an instance of a service in the controller and using the Proxy pattern to make sure the call is enrolled in a transaction.
当然,缺省状况下service都是事务的,那么,要想修改为非事务的话,如下:
class TransferService {
static transactional = false
...
}
注意:use runtime exceptions if you want to roll back a transaction
(要想回滚必须是runtime异常)
如果不是runtime异常,则可以自己catch然后抛出一个runtime异常。
Other Example
class AccountController {
def transferService
def auditService
def transfer = {
def fromAccount = params?.fromAccount
def toAccount = params?.toAccount
def amountToTransfer = params?.amount
// some validity checks, could be performed through a Command Object class
// ...
// call service to make the transfer
transferService.transfer(fromAccount, toAccount, amountToTransfer)
// notify the auditing plateform of the transfer
auditService.notifyTransfer(fromAccount, toAccount, amountToTransfer)
}
}
这里调用了两个service中的两个方法,当notifyTransfer发生错误或异常时,转账却被保存了,为什么?
这里开启了两个事务,transfer方法运行时,开启事务A,transfer方法结束时,关闭事务A,notifyTransfer方法运行时开启事务B,发生异常时,数据回滚,结束事务B。因为两个当开启事务B时,事务A已经结束了,所以即使B的数据回滚了,也不会使A的数据回滚,controller是非事务的。
解决办法:尽量在controller中引入一个service,把对多个service的调用写在另一个service中,因为service是事务的。
grails 2.3之后
grails2.3之后,使用了全局事务
可以通过注解的方式来控制是否为事务,缺省为readOnly = false,或者直接写成@Transactional
如果想把action定义为非事务的话,可以用@NotTransactional
事务回滚的原理
未使用事务:
当save的时候,数据保存到hibernate session的一级缓存中,只有当flush:true时,才把整个session提交到数据库进行保存,如果在flush:true之前错误或者异常,所有数据都不会被保存;如果在flush:true之后异常,数据已经提交到数据库,无法回滚。
使用事务之后:
当save的时候,数据保存到hibernate session的一级缓存中,用了事务,当flush:true时,把整个session提交到数据库的session的二级缓存,无论在flush:true之前还是之后发生错误或者异常,所有数据都不会被保存,数据都会回滚。