基于Spring JDBC框架的事务管理
事务(Transaction):是数据库中的一种能够保证多个写操作要么全部成功,要么全部失败的机制!
例如以下转账操作,张三给李四转了1000元,张三-1000元,李四就应该+1000元,这两条sql语句,就应该是全部成功或全部失败的,只成功一半,是不可接受的,如果张三-1000元,李四却没有+1000元,这1000元就不易而飞了:
update 账户表 set 余额=余额-1000 where 帐户名='张三';
update 账户表 set 余额=余额+1000 where 帐户名='李四';
或者比如你添加管理员, 只添加进了管理员的信息,没有添加进角色的信息,也就没有权限的信息,数据不完整,这个插入的管理员存在就是没有意义的,所以就全部需要添加成功或者失败。
在Spring Boot项目中,添加了数据库编程的依赖项后,大多都依赖了spring-jdbc
框架,例如:
在添加了spring-jdbc
依赖的项目中,当需要使得某个方法是“事务性”的,只需要使用@Transactional
注解即可!
关于此注解:
-
添加在业务实现类中的方法上,使得当前方法(重写的方法)是事务性的
-
添加在业务实现类上,使得当前类中所有重写的方法都是事务性的
-
添加在业务接口的抽象方法上,使得重写的方法是事务性的
-
添加在业务接口上,使得实现类中所有重写的方法都是事务性的
注意:Spring JDBC框架使用了基于接口的代理来实现事务管理,所有没在接口中声明的方法都不可能是事务性的!
事务的执行过程大致是:
开启事务(BEGIN)
执行业务
-- 执行成功,则提交(COMMIT)
-- 执行出错,则回滚(ROLLBACK)
因为执行成功,则提交(COMMIT),执行出错,则回滚(ROLLBACK),那么怎么算成功,怎么算失败呢:
基于Spring JDBC的事务管理中,默认将根据RuntimeException
来判断执行过程是否出错,从而回滚,即:
开启事务(BEGIN)
try {
执行业务
提交(COMMIT)
} catch (RuntimeException e) {
回滚(ROLLBACK)
}
所以我们抛异常也可以算一种错误,当我们在写业务的时候都是抛ServiceException表示这是操作失败,那么操作失败,之前的增删改就应该回滚。但如果继承的不是RuntimeException就无法回滚,这也是自定义异常要继承RuntimeException的原因。
在@Transactional
注解,还可以配置几个属性,来控制回滚:
-
rollbackFor
:指定根据哪些异常回滚,取值为异常类型的数组,例如:@Transactional(rollbackFor = {NullPointerException.class, NumberFormatException.class})
-
rollbackForClassName
:指定根据哪些异常回滚,取值为异常类型的全限定名的字符串数组,例如:@Transactional(rollbackForClassName = {"java.lang.NullPointerException", "java.lang.NumberFormatException"})
-
noRollbackFor
:指定对于哪些异常不执行回滚,取值为异常类型的数组 -
noRollbackForClassName
:指定对于哪些异常不执行回滚,取值为异常类型的全限定名的字符串数组
注意:无论怎么配置,@Transactional
只会对RuntimeException
或其子孙类异常进行回滚!
小结:
-
当某个业务涉及多次“写”操作(例如2次INSERT操作,或1次INSERT操作加1次UPDATE操作,等等)时,必须保证此业务方法是事务性的,理论上,应该按需在业务的抽象方法上添加
@Transactional
注解,在初学时,更建议将此注解添加在接口上-
另外,对于同一个业务中的多次查询,使用
@Transactional
使其是事务性的,还可以配置使得此业务的多个查询共用同一个数据库连接,则查询效率可以提升
-
-
在执行增、删、改这类“写”操作后,应该及时获取“受影响的行数”,并且,判断此值是否符合预期,如果不符合,就应该抛出
RuntimeException
或其子孙类异常,使得事务回滚