事务属性及事务分组

一、Spring事务属性

  接上一节<<Spring的事务控制>>,Spring提供了@Transactional的注解来帮助控制事务,对于这个注解中涉及的几个属性需要说明和掌握一下

  @Transactional(isolation=Isolation.DEFAULT,rollbackFor=ArithmeticException.class,timeout = -1,readOnly=false,propagation=Propagation.REQUIRED)

  propagation传播行为:定义关于客户端和被调用方法的事物边界

  isolation隔离级别:并发访问下数据库的全安性

  timeout 事务超时:事务的最长持续时间,如果事务一直没有提交或回滚,那么超出该时间后,系统将自动回滚事务,单位秒。-1表示不超时,最终由底层数据库系统决定。

  readOnly只读状态:只读事务不修改任何数据,可以优化查询操作。

  rollbackFor和noRollbackFor:指定异常回滚或不回滚

  其中关于隔离级别的配置主要有以下几种:

  1. DEFAULT (默认) 
这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别.另外四个与JDBC的隔离级别相对应 
  2.READ_UNCOMMITTED (读未提交) 
这是事务最低的隔离级别,它允许另外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。 
  3.READ_COMMITTED (读已提交) 
保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻像读。 
  4.REPEATABLE_READ (可重复读) 
这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了不可重复读 
  5.SERIALIZABLE(串行化) 
这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻像读。 

  关于事务传播属性的配置主要有以下几种:

传播行为

意义

REQUIRED

业务方法需要在一个事务中运行。如果方法运行时,已经处在一个事务中,那么加入到该事务,否则为自己创建一个新的事务

NOT_SUPPORTED

声明方法不需要事务。如果方法没有关联到一个事务,容器不会为它开启事务。如果方法在一个事务中被调用,该事务会被挂起,在方法调用结束后,原先的事务便会恢复执行

REQUIRESNEW

属性表明不管是否存在事务,业务方法总会为自己发起一个新的事务。如果方法已经运行在一个事务中,则原有事务会被挂起,新的事务会被创建,直到方法执行结束,新事务才算结束,原先的事务才会恢复执行

MANDATORY

该属性指定业务方法只能在一个已经存在的事务中执行,业务方法不能发起自己的事务。如果业务方法在没有事务的环境下调用,容器就会抛出例外。

SUPPORTS

这一事务属性表明,如果业务方法在某个事务范围内被调用,则方法成为该事务的一部分。如果业务方法在事务范围外被调用,则方法在没有事务的环境下执行

Never

指定业务方法绝对不能在事务范围内执行。如果业务方法在某个事务中执行,容器会抛出例外,只有业务方法没有关联到任何事务,才能正常执行

NESTED

如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按REQUIRED属性执行.它使用了一个单独的事务, 这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效

二、事务传播属性案例

  接着上一节的代码,假设在业务实现类PersonService中引入多个DAO,并且都涉及到了增删改操作,在都添加事务控制的前提下,如何做到各个DAO之间的事务互不影响。

  如下代码:在业务方法operateMethod中引入logDao和personDao,如果logDao和personDao的操作都在一个事务中,那么operateMethod一旦出现异常,则两个Dao中的逻辑都会被回滚掉,如果两个Dao各自的事务传播属性都是REQUIRED,由于REQUIRED的定义是:业务方法需要在一个事务中运行。如果方法运行时,已经处在一个事务中,那么加入到该事务,否则为自己创建一个新的事务。则一旦operateMethod出现异常,两个Dao依然会被同时回滚。

  如果需要做到operateMethod发生异常时,logDao不回滚,而personDao回滚,则需要将logDao的事务传播属性设置为REQUIRES_NEW,如此以来,执行到operateMethod方法时,会开启一个事务A,再走到logDao时,原先的事务A会挂起,开启一个新的事务B,待到logDao执行完毕,新的事务B提交,原先A的事务恢复,继续往下执行。

package com.jyk.spring.annotation.transaction.property;

import javax.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

/*
 * 在一个service中调用多个dao
 */
@Service
public class PersonService {

    @Resource
    private PersonDao personDao;
    
    @Resource
    private LogDao logDao;
    
    /*
     * @Transactional的使用位置
     * 1、定义到方法上:当前方法应用的spring的声明式事务
     * 2、定义到类上:当前类的所有方法都应用spring的声明式事务
     * 3、定义到父类上:当执行父类的方法的时候应用Spring的声明式事务
     */
    @Transactional(
            readOnly=false, //读写事务,只读事务不修改任何数据
            timeout = -1,//事务的超时时间没有限制
        //    noRollbackFor = ArithmeticException.class, //遇到数学异常不回滚
            isolation = Isolation.DEFAULT, //事务的隔离级别    
            propagation = Propagation.REQUIRED //传播行为
            )    
    public void operateMethod(Person person)
    {
        logDao.save();//打印日志<开启自己的事务>
        int myexception = 1/0;
        personDao.addPerson(person);//添加用户
    }
}
package com.jyk.spring.annotation.transaction.property;

import javax.annotation.Resource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Repository
public class LogDao {
    
    @Resource
    private JdbcTemplate jdbcTemplate;
    
    //在事务中自己开启自己的事务
    @Transactional(
        propagation = Propagation.REQUIRES_NEW
    )
    public void save()
    {
        String sql = "insert into mylog(content) values('正在打印日志...')";
        jdbcTemplate.update(sql);
    }
}

三、事务分组

  事务分组这一概念被广泛地应用于各大企业应用中,用来控制长链条的业务逻辑中,将业务切分为各小模块,并且各小模块处于互补影响的事务中,从而实现整个链条发生异常时,部分业务回滚,而部分业务不回滚的场景。

  假设A->B->C->D这样一个调用场景,如果想实现整个链路异常时,只有C回滚,AB和D不回滚,A或B出现异常时AB回滚,D出现异常时D才会回滚,类似这种复杂的事务控制场景,需要如何实现?

  很简单,如果看了《Spring的事务控制》和本节的内容,只要熟练的使用REQUIRE和REQUIRES_NEW即可控制,我们可将整个链路ABCD放在一个execute方法中,execute开启REQUIRE事务1,到A时使用REQUIRES_NEW属性开启新的事务2,同时B也加入到该事务2中,C加入到事务1中,同时D使用REQUIRES_NEW属性开启新的事务3,就能实现了。

  思路如下:

  REQUIRE

  execute(){
  

  AB();  

 

  C();

 

  REQUIRES_NEW

  D();

  }

 

  REQUIRES_NEW

  AB(){  

  A();

  B();

  }

posted @ 2018-07-29 21:17  纪煜楷  阅读(827)  评论(0编辑  收藏  举报