Spring事务详细解释
前言
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.NESTED
和Propagation.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事务详细解释的更多相关文章
- spring事务详细理解
数据并发的问题 一个数据库可能拥有多个访问客户端,这些客户端都可以并发方式访问数据库.数据库中的相同数据可能同时被多个事务访问,如果没有采取必要的隔离措施,就会导致各种并发问题,破坏数据的完整性.这些 ...
- Spring中IOC和AOP的详细解释(转)
原文链接:Spring中IOC和AOP的详细解释 我们是在使用Spring框架的过程中,其实就是为了使用IOC,依赖注入,和AOP,面向切面编程,这两个是Spring的灵魂. 主要用到的设计模式有工厂 ...
- Spring学习13-中IOC(工厂模式)和AOP(代理模式)的详细解释
我们是在使用Spring框架的过程中,其实就是为了使用IOC,依赖注入,和AOP,面向切面编程,这两个是Spring的灵魂. 主要用到的设计模式有工厂模式和代理模式. IOC是工厂模式参考:设计模式- ...
- Spring 事务配置管理,简单易懂,详细 [声明式]
Spring 事务配置说明 Spring 如果没有特殊说明,一般指是跟数据存储有关的数据操作事务操作:对于数据持久操作的事务配置,一般有三个对象,数据源,事务管理器,以及事务代理机制: Spring ...
- Spring事务属性具体解释
Spring.是一个Java开源框架,是为了解决企业应用程序开发复杂性由Rod Johnson创建的.框架的主要优势之中的一个就是其分层架构,分层架构同意使用者选择使用哪一个组件,同一时候为 J2EE ...
- 详细解析Spring事务的配置和OpenSessionInview的作用
1.事务的特性 原子性:事务中的操作是不可分割的一部分 一致性:要么同时成功,要么同时失败(事务执行前后数据保持一致) 隔离性:并发互不干扰 持久性:事务一旦被提交,它就是一条持久 ...
- Spring 事务 属性 详细
学习东西要知行合一,如果只是知道理论而没实践过,那么掌握的也不会特别扎实,估计过几天就会忘记,接下来我们一起实践来学习Spring事务的传播属性. 传播属性 传播属性定义的是当一个事务方法碰到另一个事 ...
- spring事务传播机制实例讲解
http://kingj.iteye.com/blog/1680350 spring事务传播机制实例讲解 博客分类: spring java历险 天温习spring的事务处理机制,总结 ...
- Spring 事务模型
一.三种事务模型 1.本地事务模型:开发人员不用知道事务的存在,事务全部交给数据库来管理,数据库自己决定什么时候提交或回滚,所以数据库是事务的管理者. Connection conn=jdbcDao. ...
随机推荐
- 14. Django MTV及Django模型
MTV 我们或许都听说过MVC模式.MVC是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑.数据.界面显示分离的方法组织代码.Django ...
- Cai_Sublime
Cai_Sublime Package Control:插件包管理工具 The simplest method of installation is through the Sublime Text ...
- 如何取消IDEA的自动删除行尾空格?
使用IDEA,添加注释的时候敲空格,总是会把行尾空格删除导致代码跑到注释行,很不爽~~ 取消这个不爽的功能:File--Settings--Editor--General--Other--Strip ...
- redis 发布订阅(pub/sub )
- C# JavaScriptSerializer 自定义序列化
虽然,我个人建议使用Json.Net. 但大家需求不同.遇到一个朋友,他有个需求JavaScriptSerializer并且序列化时,隐藏基类成员. 这里我采用自定义序列化来实现: public st ...
- PHP-组合总和
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合. candidates 中的数字可以无限制重复被选 ...
- Delphi 滚动条的使用
DELPHI的滚动条默认发送消息格式: function TControl.Perform( Msg: Cardinal; WParam: WPARAM; LParam: LPARAM): LRESU ...
- putty字体和颜色修改
来源:https://www.igvita.com/2008/04/14/custom-putty-color-themes/ 网站上有putty的注册表,可以修改putty显示的配色方案. 非常棒!
- Android中attrs.xml文件的使用详解
$*********************************************************************************************$ 博主推荐 ...
- Minimum Snap轨迹规划详解(3)闭式求解
如果QP问题只有等式约束没有不等式约束,那么是可以闭式求解(close form)的.闭式求解效率要快很多,而且只需要用到矩阵运算,不需要QPsolver. 这里介绍Nicholas Roy文章中闭式 ...