前言

Spring在TransactionDefinition接口中规定了7种类型的事务传播行为。事务传播行为是Spring框架独有的事务增强特性,他不属于的事务实际提供方数据库行为。这是Spring为我们提供的强大的工具箱,使用事务传播行可以为我们的开发工作提供许多便利。但是人们对他的误解也颇多,你一定也听过“service方法事务最好不要嵌套”的传言。要想正确的使用工具首先需要了解工具。本文对七种事务传播行为做详细介绍,内容主要代码示例的方式呈现。

基础概念

1. 什么是事务传播行为?

事务传播行为用来描述由某一个事务传播行为修饰的方法被嵌套进另一个方法的时事务如何传播。

用伪代码说明:

 public void methodA(){

    methodB();

    //doSomething

 }

 @Transaction(Propagation=XXX)

 public void methodB(){

    //doSomething

 }

代码中methodA()方法嵌套调用了methodB()方法,methodB()的事务传播行为由@Transaction(Propagation=XXX)设置决定。这里需要注意的是methodA()并没有开启事务,某一个事务传播行为修饰的方法并不是必须要在开启事务的外围方法中调用。

2. Spring中七种事务传播行为

定义非常简单,也很好理解,下面我们就进入代码测试部分,验证我们的理解是否正确。

代码验证

文中代码以传统三层结构中两层呈现,即Service和Dao层,由Spring负责依赖注入和注解式事务管理,DAO层由Mybatis实现,你也可以使用任何喜欢的方式,例如,Hibernate,JPA,JDBCTemplate等。数据库使用的是MySQL数据库,你也可以使用任何支持事务的数据库,并不会影响验证结果。

首先我们在数据库中创建两张表:

user1

CREATE TABLE `user1` (

  `id` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,

  `name` VARCHAR(45) NOT NULL DEFAULT '',

  PRIMARY KEY(`id`)

)

ENGINE = InnoDB;

user2

CREATE TABLE `user2` (

  `id` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,

  `name` VARCHAR(45) NOT NULL DEFAULT '',

  PRIMARY KEY(`id`)

)

ENGINE = InnoDB;

然后编写相应的Bean和DAO层代码:

User1

public class User1 {

    private Integer id;

    private String name;

   //get和set方法省略...

}

User2

public class User2 {

    private Integer id;

    private String name;

   //get和set方法省略...

}

User1Mapper

public interface User1Mapper {

    int insert(User1 record);

    User1 selectByPrimaryKey(Integer id);

    //其他方法省略...

}

User2Mapper

public interface User2Mapper {

    int insert(User2 record);

    User2 selectByPrimaryKey(Integer id);

    //其他方法省略...

}

最后也是具体验证的代码由service层实现,下面我们分情况列举。

1.PROPAGATION_REQUIRED

我们为User1Service和User2Service相应方法加上Propagation.REQUIRED属性。

User1Service方法:

@Service

public class User1ServiceImpl implements User1Service {

    //省略其他...

    @Override

    @Transactional(propagation = Propagation.REQUIRED)

    public void addRequired(User1 user){

        user1Mapper.insert(user);

    }

}

User2Service方法:

@Service

public class User2ServiceImpl implements User2Service {

    //省略其他...

    @Override

    @Transactional(propagation = Propagation.REQUIRED)

    public void addRequired(User2 user){

        user2Mapper.insert(user);

    }

    @Override

    @Transactional(propagation = Propagation.REQUIRED)

    public void addRequiredException(User2 user){

        user2Mapper.insert(user);

        throw new RuntimeException();

    }

}

1.1 场景一

此场景外围方法没有开启事务。

验证方法1:

    @Override

    public void notransaction_exception_required_required(){

        User1 user1=new User1();

        user1.setName("张三");

        user1Service.addRequired(user1);

        User2 user2=new User2();

        user2.setName("李四");

        user2Service.addRequired(user2);

        throw new RuntimeException();

    }

验证方法2:

    @Override

    public void notransaction_required_required_exception(){

        User1 user1=new User1();

        user1.setName("张三");

        user1Service.addRequired(user1);

        User2 user2=new User2();

        user2.setName("李四");

        user2Service.addRequiredException(user2);

    }

分别执行验证方法,结果:

结论:通过这两个方法我们证明了在外围方法未开启事务的情况下Propagation.REQUIRED修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。

1.2 场景二

外围方法开启事务,这个是使用率比较高的场景。

验证方法1:

   @Override

    @Transactional(propagation = Propagation.REQUIRED)

    public void transaction_exception_required_required(){

        User1 user1=new User1();

        user1.setName("张三");

        user1Service.addRequired(user1);

        User2 user2=new User2();

        user2.setName("李四");

        user2Service.addRequired(user2);

        throw new RuntimeException();

    }

验证方法2:

    @Override

    @Transactional(propagation = Propagation.REQUIRED)

    public void transaction_required_required_exception(){

        User1 user1=new User1();

        user1.setName("张三");

        user1Service.addRequired(user1);

        User2 user2=new User2();

        user2.setName("李四");

        user2Service.addRequiredException(user2);

    }

验证方法3:

    @Transactional

    @Override

    public void transaction_required_required_exception_try(){

        User1 user1=new User1();

        user1.setName("张三");

        user1Service.addRequired(user1);

        User2 user2=new User2();

        user2.setName("李四");

        try {

            user2Service.addRequiredException(user2);

        } catch (Exception e) {

            System.out.println("方法回滚");

        }

    }

分别执行验证方法,结果:

结论:以上试验结果我们证明在外围方法开启事务的情况下Propagation.REQUIRED修饰的内部方法会加入到外围方法的事务中,所有Propagation.REQUIRED修饰的内部方法和外围方法均属于同一事务,只要一个方法回滚,整个事务均回滚。

2.PROPAGATION_REQUIRES_NEW

我们为User1Service和User2Service相应方法加上Propagation.REQUIRES_NEW属性。
User1Service方法:

@Service

public class User1ServiceImpl implements User1Service {

    //省略其他...

    @Override

    @Transactional(propagation = Propagation.REQUIRES_NEW)

    public void addRequiresNew(User1 user){

        user1Mapper.insert(user);

    }

    @Override

    @Transactional(propagation = Propagation.REQUIRED)

    public void addRequired(User1 user){

        user1Mapper.insert(user);

    }

}

User2Service方法:

@Service

public class User2ServiceImpl implements User2Service {

    //省略其他...

    @Override

    @Transactional(propagation = Propagation.REQUIRES_NEW)

    public void addRequiresNew(User2 user){

        user2Mapper.insert(user);

    }

    @Override

    @Transactional(propagation = Propagation.REQUIRES_NEW)

    public void addRequiresNewException(User2 user){

        user2Mapper.insert(user);

        throw new RuntimeException();

    }

}

2.1 场景一

外围方法没有开启事务。

验证方法1:

    @Override

    public void notransaction_exception_requiresNew_requiresNew(){

        User1 user1=new User1();

        user1.setName("张三");

        user1Service.addRequiresNew(user1);

        User2 user2=new User2();

        user2.setName("李四");

        user2Service.addRequiresNew(user2);

        throw new RuntimeException();

    }

验证方法2:

    @Override

    public void notransaction_requiresNew_requiresNew_exception(){

        User1 user1=new User1();

        user1.setName("张三");

        user1Service.addRequiresNew(user1);

        User2 user2=new User2();

        user2.setName("李四");

        user2Service.addRequiresNewException(user2);

    }

分别执行验证方法,结果:

结论:通过这两个方法我们证明了在外围方法未开启事务的情况下Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。

2.2 场景二

外围方法开启事务。

验证方法1:

    @Override

    @Transactional(propagation = Propagation.REQUIRED)

    public void transaction_exception_required_requiresNew_requiresNew(){

        User1 user1=new User1();

        user1.setName("张三");

        user1Service.addRequired(user1);

        User2 user2=new User2();

        user2.setName("李四");

        user2Service.addRequiresNew(user2);

        User2 user3=new User2();

        user3.setName("王五");

        user2Service.addRequiresNew(user3);

        throw new RuntimeException();

    }

验证方法2:

    @Override

    @Transactional(propagation = Propagation.REQUIRED)

    public void transaction_required_requiresNew_requiresNew_exception(){

        User1 user1=new User1();

        user1.setName("张三");

        user1Service.addRequired(user1);

        User2 user2=new User2();

        user2.setName("李四");

        user2Service.addRequiresNew(user2);

        User2 user3=new User2();

        user3.setName("王五");

        user2Service.addRequiresNewException(user3);

    }

验证方法3:

    @Override

    @Transactional(propagation = Propagation.REQUIRED)

    public void transaction_required_requiresNew_requiresNew_exception_try(){

        User1 user1=new User1();

        user1.setName("张三");

        user1Service.addRequired(user1);

        User2 user2=new User2();

        user2.setName("李四");

        user2Service.addRequiresNew(user2);

        User2 user3=new User2();

        user3.setName("王五");

        try {

            user2Service.addRequiresNewException(user3);

        } catch (Exception e) {

            System.out.println("回滚");

        }

    }

分别执行验证方法,结果:

结论:在外围方法开启事务的情况下Propagation.REQUIRES_NEW修饰的内部方法依然会单独开启独立事务,且与外部方法事务也独立,内部方法之间、内部方法和外部方法事务均相互独立,互不干扰。

3.PROPAGATION_NESTED

我们为User1Service和User2Service相应方法加上Propagation.NESTED属性。
User1Service方法:

@Service

public class User1ServiceImpl implements User1Service {

    //省略其他...

    @Override

    @Transactional(propagation = Propagation.NESTED)

    public void addNested(User1 user){

        user1Mapper.insert(user);

    }

}

User2Service方法:

@Service

public class User2ServiceImpl implements User2Service {

    //省略其他...

    @Override

    @Transactional(propagation = Propagation.NESTED)

    public void addNested(User2 user){

        user2Mapper.insert(user);

    }

    @Override

    @Transactional(propagation = Propagation.NESTED)

    public void addNestedException(User2 user){

        user2Mapper.insert(user);

        throw new RuntimeException();

    }

}

3.1 场景一

此场景外围方法没有开启事务。

验证方法1:

    @Override

    public void notransaction_exception_nested_nested(){

        User1 user1=new User1();

        user1.setName("张三");

        user1Service.addNested(user1);

        User2 user2=new User2();

        user2.setName("李四");

        user2Service.addNested(user2);

        throw new RuntimeException();

    }

验证方法2:

    @Override

    public void notransaction_nested_nested_exception(){

        User1 user1=new User1();

        user1.setName("张三");

        user1Service.addNested(user1);

        User2 user2=new User2();

        user2.setName("李四");

        user2Service.addNestedException(user2);

    }

分别执行验证方法,结果:

结论:通过这两个方法我们证明了在外围方法未开启事务的情况下Propagation.NESTEDPropagation.REQUIRED作用相同,修饰的内部方法都会新开启自己的事务,且开启的事务相互独立,互不干扰。

3.2 场景二

外围方法开启事务。

验证方法1:

    @Transactional

    @Override

    public void transaction_exception_nested_nested(){

        User1 user1=new User1();

        user1.setName("张三");

        user1Service.addNested(user1);

        User2 user2=new User2();

        user2.setName("李四");

        user2Service.addNested(user2);

        throw new RuntimeException();

验证方法2

 
    @Transactional

    @Override

    public void transaction_nested_nested_exception(){

        User1 user1=new User1();

        user1.setName("张三");

        user1Service.addNested(user1);

        User2 user2=new User2();

        user2.setName("李四");

        user2Service.addNestedException(user2);

    }

  验证方法3:

    @Transactional

    @Override

    public void transaction_nested_nested_exception_try(){

        User1 user1=new User1();

        user1.setName("张三");

        user1Service.addNested(user1);

        User2 user2=new User2();

        user2.setName("李四");

        try {

            user2Service.addNestedException(user2);

        } catch (Exception e) {

            System.out.println("方法回滚");

        }

    }

 分别执行验证方法,结果:

 

结论:以上试验结果我们证明在外围方法开启事务的情况下Propagation.NESTED修饰的内部方法属于外部事务的子事务,外围主事务回滚,子事务一定回滚,而内部子事务可以单独回滚而不影响外围主事务和其他子事务

4. REQUIRED,REQUIRES_NEW,NESTED异同

由“1.2 场景二”和“3.2 场景二”对比,我们可知:
NESTED和REQUIRED修饰的内部方法都属于外围方法事务,如果外围方法抛出异常,这两种方法的事务都会被回滚。但是REQUIRED是加入外围方法事务,所以和外围事务同属于一个事务,一旦REQUIRED事务抛出异常被回滚,外围方法事务也将被回滚。而NESTED是外围方法的子事务,有单独的保存点,所以NESTED方法抛出异常被回滚,不会影响到外围方法的事务。

由“2.2 场景二”和“3.2 场景二”对比,我们可知:
NESTED和REQUIRES_NEW都可以做到内部方法事务回滚而不影响外围方法事务。但是因为NESTED是嵌套事务,所以外围方法回滚之后,作为外围方法事务的子事务也会被回滚。而REQUIRES_NEW是通过开启新的事务实现的,内部事务和外围事务是两个事务,外围事务回滚不会影响内部事务。

5. 其他事务传播行为

鉴于文章篇幅问题,其他事务传播行为的测试就不在此一一描述了,感兴趣的读者可以去源码中自己寻找相应测试代码和结果解释

模拟用例

介绍了这么多事务传播行为,我们在实际工作中如何应用呢?下面我来举一个示例:

假设我们有一个注册的方法,方法中调用添加积分的方法,如果我们希望添加积分不会影响注册流程(即添加积分执行失败回滚不能使注册方法也回滚),我们会这样写:

   @Service

   public class UserServiceImpl implements UserService {

        @Transactional

        public void register(User user){

            try {

                membershipPointService.addPoint(Point point);

            } catch (Exception e) {

               //省略...

            }

            //省略...

        }

        //省略...

   }

我们还规定注册失败要影响addPoint()方法(注册方法回滚添加积分方法也需要回滚),那么addPoint()方法就需要这样实现:

   @Service

   public class MembershipPointServiceImpl implements MembershipPointService{

        @Transactional(propagation = Propagation.NESTED)

        public void addPoint(Point point){

            try {

                recordService.addRecord(Record record);

            } catch (Exception e) {

               //省略...

            }

            //省略...

        }

        //省略...

   }

我们注意到了在addPoint()中还调用了addRecord()方法,这个方法用来记录日志。他的实现如下:

   @Service

   public class RecordServiceImpl implements RecordService{

        @Transactional(propagation = Propagation.NOT_SUPPORTED)

        public void addRecord(Record record){

            //省略...

        }

        //省略...

   }

我们注意到addRecord()方法中propagation = Propagation.NOT_SUPPORTED,因为对于日志无所谓精确,可以多一条也可以少一条,所以addRecord()方法本身和外围addPoint()方法抛出异常都不会使addRecord()方法回滚,并且addRecord()方法抛出异常也不会影响外围addPoint()方法的执行。

通过这个例子相信大家对事务传播行为的使用有了更加直观的认识,通过各种属性的组合确实能让我们的业务实现更加灵活多样。

获取更多学习资料,可以加群:473984645或扫描下方二维码

Spring事务详细解释的更多相关文章

  1. spring事务详细理解

    数据并发的问题 一个数据库可能拥有多个访问客户端,这些客户端都可以并发方式访问数据库.数据库中的相同数据可能同时被多个事务访问,如果没有采取必要的隔离措施,就会导致各种并发问题,破坏数据的完整性.这些 ...

  2. Spring中IOC和AOP的详细解释(转)

    原文链接:Spring中IOC和AOP的详细解释 我们是在使用Spring框架的过程中,其实就是为了使用IOC,依赖注入,和AOP,面向切面编程,这两个是Spring的灵魂. 主要用到的设计模式有工厂 ...

  3. Spring学习13-中IOC(工厂模式)和AOP(代理模式)的详细解释

    我们是在使用Spring框架的过程中,其实就是为了使用IOC,依赖注入,和AOP,面向切面编程,这两个是Spring的灵魂. 主要用到的设计模式有工厂模式和代理模式. IOC是工厂模式参考:设计模式- ...

  4. Spring 事务配置管理,简单易懂,详细 [声明式]

    Spring 事务配置说明 Spring 如果没有特殊说明,一般指是跟数据存储有关的数据操作事务操作:对于数据持久操作的事务配置,一般有三个对象,数据源,事务管理器,以及事务代理机制: Spring ...

  5. Spring事务属性具体解释

    Spring.是一个Java开源框架,是为了解决企业应用程序开发复杂性由Rod Johnson创建的.框架的主要优势之中的一个就是其分层架构,分层架构同意使用者选择使用哪一个组件,同一时候为 J2EE ...

  6. 详细解析Spring事务的配置和OpenSessionInview的作用

    1.事务的特性   原子性:事务中的操作是不可分割的一部分   一致性:要么同时成功,要么同时失败(事务执行前后数据保持一致)   隔离性:并发互不干扰     持久性:事务一旦被提交,它就是一条持久 ...

  7. Spring 事务 属性 详细

    学习东西要知行合一,如果只是知道理论而没实践过,那么掌握的也不会特别扎实,估计过几天就会忘记,接下来我们一起实践来学习Spring事务的传播属性. 传播属性 传播属性定义的是当一个事务方法碰到另一个事 ...

  8. spring事务传播机制实例讲解

    http://kingj.iteye.com/blog/1680350   spring事务传播机制实例讲解 博客分类:   spring java历险     天温习spring的事务处理机制,总结 ...

  9. Spring 事务模型

    一.三种事务模型 1.本地事务模型:开发人员不用知道事务的存在,事务全部交给数据库来管理,数据库自己决定什么时候提交或回滚,所以数据库是事务的管理者. Connection conn=jdbcDao. ...

随机推荐

  1. 20. Cookie 和 Session

    之前我们在Cookie 和Session是什么?已经说过Cookie 和Session,但是为了保证系列的完整性,我们决定重新说一遍,当然可能会有一些区别,建议先从Cookie 和Session是什么 ...

  2. linux shell unzip multiple zip files

    find . -name "*.result.zip" | xargs -n 1 unzip - -P password -d ../ext_logs

  3. Forgery CodeForces - 1059B

    一道印章刻印的题目: 具体要求:有一个固定的3*3的印章,给你一个墨迹问能用这个印章印出墨迹吗?(一个像素可以多次被上色) 输入:第一行是墨迹的行列规模,接下来是墨迹 输出:If Andrey can ...

  4. upc组队赛14 Evolution Game【dp】

    Evolution Game 题目描述 In the fantasy world of ICPC there are magical beasts. As they grow, these beast ...

  5. Config JAVA evironment for LoadRunner

    1. Install jdk 2. Set system variables eg. JAVA_HOME = C:\Program Files (x86)\Java\jdk1.6.0_43 class ...

  6. Excel解析工具POI

    HSSFWorkbook:是操作Excel2003以前(包括2003)的版本,扩展名是.xls XSSFWorkbook:是操作Excel2007的版本,扩展名是.xlsx 对于不同版本的EXCEL文 ...

  7. PHP操作XML方法之 XML Expat Parser

    XML Expat Parser 简介 此PHP扩展实现了使用PHP支持JamesClark编写的expat.此工具包可解析(但不能验证)XML文档.它支持PHP所提供的3种字符编码:US-ASCII ...

  8. .net EntityFramework dbContext 如何实例化

    1 .DbContext怎么在Asp.mvc中使如何实例化 public class Repository { //实例化EF容器:有弊端.一个线程里可能会创建多个DbContext //DbCont ...

  9. termcap - 终端功能数据库

    描述 DESCRIPTION termcap 数据库是一个过时 (obsolete) 工具,用来描述以字符为单位的终端和打印机的功能.它之所以被保留,是为了兼容古老的程序:新程序应当使用 termin ...

  10. mysql数据库用户权限设置

    设置用户权限:格式:grant 权限列表 on 数据库名.表名 to '用户名'@'来源地址' identified by '密码'; * 权限列表:用于列出授权的各种数据库操作,通过逗号进行分割,如 ...