在Spring中,事务有两种实现方式,分别是编程式事务管理和声明式事务管理两种方式
事务管理方式:
- 编程式事务管理: 编程式事务管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。
- 声明式事务管理: 建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
声明式事务管理不需要入侵代码,通过@Transactional就可以进行事务操作,更快捷而且简单,推荐使用。
事务提交方式
- 默认情况下,数据库处于自动提交模式。每一条语句处于一个单独的事务中,在这条语句执行完毕时,如果执行成功则隐式的提交事务,如果执行失败则隐式的回滚事务。
- 对于正常的事务管理,是一组相关的操作处于一个事务之中,因此必须关闭数据库的自动提交模式。spring会将底层连接的自动提交特性设置为false。也就是在使用spring进行事物管理的时候,spring会将是否自动提交设置为false,等价于JDBC中的 connection.setAutoCommit(false);在执行完之后在进行提交,connection.commit();
隔离级别是指若干个并发的事务之间的隔离程度。TransactionDefinition 接口中定义了五个表示隔离级别的常量:
- TransactionDefinition.ISOLATION_DEFAULT:这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是TransactionDefinition.ISOLATION_READ_COMMITTED。
- TransactionDefinition.ISOLATION_READ_UNCOMMITTED:该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读,不可重复读和幻读,因此很少使用该隔离级别。比如PostgreSQL实际上并没有此级别。
- TransactionDefinition.ISOLATION_READ_COMMITTED:该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
- TransactionDefinition.ISOLATION_REPEATABLE_READ:该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。该级别可以防止脏读和不可重复读。
- TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
事务传播行为
所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。在TransactionDefinition定义中包括了如下几个表示传播行为的常量:
- TransactionDefinition.PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。
- TransactionDefinition.PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
- TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
- TransactionDefinition.PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
- TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
事务回滚规则
指示spring事务管理器回滚一个事务的推荐方法是在当前事务的上下文内抛出异常。spring事务管理器会捕捉任何未处理的异常,然后依据规则决定是否回滚抛出异常的事务。
默认配置下,spring只有在抛出的异常为运行时unchecked异常时才回滚该事务,也就是抛出的异常为RuntimeException的子类(Errors也会导致事务回滚),而抛出checked异常则不会导致事务回滚。
可以明确的配置在抛出那些异常时回滚事务,包括checked异常。也可以明确定义那些异常抛出时不回滚事务。
事务常用配置
- readOnly:该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。例如:@Transactional(readOnly=true);
- rollbackFor: 该属性用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。例如:指定单一异常类:@Transactional(rollbackFor=RuntimeException.class)指定多个异常类:@Transactional(rollbackFor={RuntimeException.class, Exception.class});
- rollbackForClassName: 该属性用于设置需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,则进行事务回滚。例如:指定单一异常类名称@Transactional(rollbackForClassName=”RuntimeException”)指定多个异常类名称:@Transactional(rollbackForClassName={“RuntimeException”,”Exception”})。
- noRollbackFor:该属性用于设置不需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,不进行事务回滚。例如:指定单一异常类:@Transactional(noRollbackFor=RuntimeException.class)指定多个异常类:@Transactional(noRollbackFor={RuntimeException.class, Exception.class})。
- noRollbackForClassName:该属性用于设置不需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,不进行事务回滚。例如:指定单一异常类名称:@Transactional(noRollbackForClassName=”RuntimeException”)指定多个异常类名称:@Transactional(noRollbackForClassName={“RuntimeException”,”Exception”})。
- propagation : 该属性用于设置事务的传播行为。例如:@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true)。
- isolation:该属性用于设置底层数据库的事务隔离级别,事务隔离级别用于处理多事务并发的情况,通常使用数据库的默认隔离级别即可,基本不需要进行设置。
- timeout:该属性用于设置事务的超时秒数,默认值为-1表示永不超时。
@Transactional事务几点注意
- 默认遇到throw new RuntimeException(“…”);会回滚
- 需要捕获的throw new Exception(“…”);不会回滚
- 指定回滚
@Transactional(rollbackFor=Exception.class) public void methodName() { // 不会回滚 throw new Exception("..."); }
- 指定不回滚
@Transactional(noRollbackFor=Exception.class) public ItimDaoImpl getItemDaoImpl() { // 会回滚 throw new RuntimeException("注释"); }
- 如果有事务,那么加入事务,没有的话新建一个(不写的情况下)
@Transactional(propagation=Propagation.REQUIRED)
- 容器不为这个方法开启事务
@Transactional(propagation=Propagation.NOT_SUPPORTED)
- readOnly=true只读,不能更新,删除
@Transactional (propagation = Propagation.REQUIRED,readOnly=true)
- 设置超时时间
@Transactional (propagation = Propagation.REQUIRED,timeout=30)
- 设置数据库隔离级别
@Transactional (propagation = Propagation.REQUIRED,isolation=Isolation.DEFAULT)
- 不要在接口上声明@Transactional ,而要在具体类的方法上使用 @Transactional 注解,否则注解可能无效。
- 不要图省事将 @Transactional 放置在类级的声明中 放在类声明 会使得所有方法都有事务 故 @Transactional应该放在方法级别 不需要使用事务的方法,就不要放置事务,比如查询方法。否则对性能是有影响的
- 使用了@Transactional的方法,对同一个类里面的方法调用, @Transactional无效。比如有一个类Test,它的一个方法A,A再调用Test本类的方法B(不管B是否public还是private),但A没有声明注解事务,而B有。则外部调用A之后,B的事务是不会起作用的。
- 使用了@Transactional的方法,只能是public,@Transactional注解的方法都是被外部其他类调用才有效,故只能是public。道理和上面的有关联。故在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,它也不会报错,但事务无效。
- spring的事务在抛异常的时候会回滚,如果是catch捕获了,事务无效。可以在catch里面加上throw new RuntimeException();
- 最后有个关键的一点:和锁同时使用需要注意:由于Spring事务是通过AOP实现的,所以在方法执行之前会有开启事务,之后会有提交事务逻辑。而synchronized代码块执行是在事务之内执行的,可以推断在synchronized代码块执行完时,事务还未提交,其他线程进入synchronized代码块后,读取的数据不是最新的。
所以必须使synchronized锁的范围大于事务控制的范围,把synchronized加到Controller层或者大于事务边界的调用层!
- 代码测试
@Transactional public boolean test1(User user) throws Exception { long id = user.getId(); System.out.println("查询的数据1:" + udao.findById(id)); // 新增两次,会出现主键ID冲突,看是否可以回滚该条数据 udao.insert(user); System.out.println("查询的数据2:" + udao.findById(id)); udao.insert(user); return false; }
@Transactional public boolean test2(User user) { long id = user.getId(); try { System.out.println("查询的数据1:" + udao.findById(id)); // 新增两次,会出现主键ID冲突,看是否可以回滚该条数据 udao.insert(user); System.out.println("查询的数据2:" + udao.findById(id)); udao.insert(user); } catch (Exception e) { System.out.println("发生异常,进行手动回滚!"); // 手动回滚事物 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); e.printStackTrace(); } return false; }
@Transactional public boolean test3(User user) { /* * 子方法出现异常进行回滚 */ try { System.out.println("查询的数据1:" + udao.findById(user.getId())); deal1(user); deal2(user); deal3(user); } catch (Exception e) { System.out.println("发生异常,进行手动回滚!"); // 手动回滚事物 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); e.printStackTrace(); } return false; } public void deal1(User user) throws SQLException { udao.insert(user); System.out.println("查询的数据2:" + udao.findById(user.getId())); } public void deal2(User user) throws SQLException{ if(user.getAge()<20){ //SQL异常 udao.insert(user); }else{ user.setAge(21); udao.update(user); System.out.println("查询的数据3:" + udao.findById(user.getId())); } } @Transactional(rollbackFor = SQLException.class) public void deal3(User user) { if(user.getAge()>20){ //SQL异常 udao.insert(user); } }
@Autowired private DataSourceTransactionManager dataSourceTransactionManager; @Autowired private TransactionDefinition transactionDefinition; public boolean test4(User user) { /* * 手动进行事物控制 */ TransactionStatus transactionStatus=null; boolean isCommit = false; try { transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition); System.out.println("查询的数据1:" + udao.findById(user.getId())); // 进行新增/修改 udao.insert(user); System.out.println("查询的数据2:" + udao.findById(user.getId())); if(user.getAge()<20) { user.setAge(user.getAge()+2); udao.update(user); System.out.println("查询的数据3:" + udao.findById(user.getId())); }else { throw new Exception("模拟一个异常!"); } //手动提交 dataSourceTransactionManager.commit(transactionStatus); isCommit= true; System.out.println("手动提交事物成功!"); throw new Exception("模拟第二个异常!"); } catch (Exception e) { //如果未提交就进行回滚 if(!isCommit){ System.out.println("发生异常,进行手动回滚!"); //手动回滚事物 dataSourceTransactionManager.rollback(transactionStatus); } e.printStackTrace(); } return false; }
Object savePoint =null; try{ //设置回滚点 savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint(); }catch(Exception e){ //出现异常回滚到savePoint。 TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint); }