Springboot下事务管理的简单使用
关于事务管理的概念这里就不多介绍了,在我的博客“JDBC事务之理论篇”中也有介绍。
关于Spring的事务管理,主要是通过事务管理器来进行的。这里看个Spring事务管理的接口图:(来自博客https://www.cnblogs.com/yixianyixian/p/8372832.html)
大概就是TransactionDefinition接口的实现类对对事务的一些配置设置进行定义;PlatformTransaction接口的实现类,是对事务进行管理的,像commit啊rollback等操作;然后TransactionStatus接口的实现类,是获得事务的一些状态的。
然后,对于事务管理,Spring提供了声明式和编程式的方法。 所谓编程式,就是在业务逻辑代码中精确地定位事务管理,而声明式事务管理,是基于AOP的,也就是说可以将事务管理代码和业务逻辑代码解耦。
在SpringBoot中使用Spring的事务管理非常简单,相当于Spring声明式事务管理,而且是注释版的。
我们只需要用两个注解:
- @EnableTransactionManagement
- @Transactional
@EnableTransactionManagement是要加载SpringBoot启动类上的,相当于是配置文件中的:
- <!-- 声明式事务管理 配置事物的注解方式注入-->
- <tx:annotation-driven transaction-manager="transactionManager"/>
而这个@Transaction是放在你要进行事务管理的类或者是方法上的。
在@Transaction注解中有一些属性可以设置:
@Transactional注解中常用参数说明
参数名称 |
功能描述 |
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 |
该属性用于设置事务的传播行为,具体取值可参考表6-7。 例如:@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true) |
isolation |
该属性用于设置底层数据库的事务隔离级别,事务隔离级别用于处理多事务并发的情况,通常使用数据库的默认隔离级别即可,基本不需要进行设置 |
timeout |
该属性用于设置事务的超时秒数,默认值为-1表示永不超时 |
这里要讲的也就设置的比较多的两个:
首先是propagation。翻译过来是传播行为。什么意思呢?
我们一般都是将事务设置在Service层 那么当我们调用Service层的一个方法的时候它能够保证我们的这个方法中执行的所有的对数据库的更新操作保持在一个事务中,在事务层里面调用的这些方法要么全部成功,要么全部失败。那么事务的传播特性也是从这里说起的。
如果你在你的Service层的这个方法中,除了调用了Dao层的方法之外,还调用了本类的其他的Service方法,那么在调用其他的Service方法的时候,这个事务是怎么规定的呢,我必须保证我在我方法里掉用的这个方法与我本身的方法处在同一个事务中,否则如果保证事物的一致性。事务的传播特性就是解决这个问题的,“事务是会传播的”在Spring中有针对传播特性的多种配置我们大多数情况下只用其中的一种:PROPGATION_REQUIRED:这个配置项的意思是说当我调用service层的方法的时候开启一个事务(具体调用那一层的方法开始创建事务,要看你的aop的配置),那么在调用这个service层里面的其他的方法的时候,如果当前方法产生了事务就用当前方法产生的事务,否则就创建一个新的事务。这个工作使由Spring来帮助我们完成的。
这里做一个各个Propagation的汇总:
- 1: PROPAGATION_REQUIRED
- 加入当前正要执行的事务不在另外一个事务里,那么就起一个新的事务
- 比如说,ServiceB.methodB的事务级别定义为PROPAGATION_REQUIRED, 那么由于执行ServiceA.methodA的时候,
- ServiceA.methodA已经起了事务,这时调用ServiceB.methodB,ServiceB.methodB看到自己已经运行在ServiceA.methodA
- 的事务内部,就不再起新的事务。而假如ServiceA.methodA运行的时候发现自己没有在事务中,他就会为自己分配一个事务。
- 这样,在ServiceA.methodA或者在ServiceB.methodB内的任何地方出现异常,事务都会被回滚。即使ServiceB.methodB的事务已经被
- 提交,但是ServiceA.methodA在接下来fail要回滚,ServiceB.methodB也要回滚
- 2: PROPAGATION_SUPPORTS
- 如果当前在事务中,即以事务的形式运行,如果当前不再一个事务中,那么就以非事务的形式运行
- 这就跟平常用的普通非事务的代码只有一点点区别了。不理这个,因为我也没有觉得有什么区别
- 3: PROPAGATION_MANDATORY
- 必须在一个事务中运行。也就是说,他只能被一个父事务调用。否则,他就要抛出异常。
- 4: PROPAGATION_REQUIRES_NEW
- 这个就比较绕口了。 比如我们设计ServiceA.methodA的事务级别为PROPAGATION_REQUIRED,ServiceB.methodB的事务级别为PROPAGATION_REQUIRES_NEW,
- 那么当执行到ServiceB.methodB的时候,ServiceA.methodA所在的事务就会挂起,ServiceB.methodB会起一个新的事务,等待ServiceB.methodB的事务完成以后,
- 他才继续执行。他与PROPAGATION_REQUIRED 的事务区别在于事务的回滚程度了。因为ServiceB.methodB是新起一个事务,那么就是存在
- 两个不同的事务。如果ServiceB.methodB已经提交,那么ServiceA.methodA失败回滚,ServiceB.methodB是不会回滚的。如果ServiceB.methodB失败回滚,
- 如果他抛出的异常被ServiceA.methodA捕获,ServiceA.methodA事务仍然可能提交。
- 5: PROPAGATION_NOT_SUPPORTED
- 当前不支持事务。比如ServiceA.methodA的事务级别是PROPAGATION_REQUIRED ,而ServiceB.methodB的事务级别是PROPAGATION_NOT_SUPPORTED ,
- 那么当执行到ServiceB.methodB时,ServiceA.methodA的事务挂起,而他以非事务的状态运行完,再继续ServiceA.methodA的事务。
- 6: PROPAGATION_NEVER
- 不能在事务中运行。假设ServiceA.methodA的事务级别是PROPAGATION_REQUIRED, 而ServiceB.methodB的事务级别是PROPAGATION_NEVER ,
- 那么ServiceB.methodB就要抛出异常了。
- 7: PROPAGATION_NESTED
- 理解Nested的关键是savepoint。他与PROPAGATION_REQUIRES_NEW的区别是,PROPAGATION_REQUIRES_NEW另起一个事务,将会与他的父事务相互独立,
- 而Nested的事务和他的父事务是相依的,他的提交是要等和他的父事务一块提交的。也就是说,如果父事务最后回滚,他也要回滚的。
- 而Nested事务的好处是他有一个savepoint。
一般用的较多就这个PROPAGATION_REQUIRED
然后还要讲的要给属性是: Isolation,就是确认隔离机制。
这个就和JDBC中的隔离机制基本一致,是为了防止drity reads、non-repeatable reads、phantom reads而有的机制,隔离级别常量这里也不细讲了,和我的博客中记录的“JDBC事务管理理论篇”中的基本一样。
好的下面上代码来看例子:
在我们的service层中的一个类中新建一个测试方法:
先在接口中添加声明:
- public interface UserService {
- Map<String, Object> selectList(Map<String, Object> map);
- Map<String, Object> insert(User user);
- Map<String, Object> update(User user);
- Map<String, Object> delete(String id);
- void transactionTest(User user1, User user2);
- }
再看实现类:(其他方法的实现省略了)
- @Service
- public class UserServiceImpl implements UserService {
- @Autowired
- private UserMapper userMapper;
- @Override
- public void transactionTest(User user1, User user2) {
- // TODO Auto-generated method stub
- userMapper.insert(user1);
- System.out.println(1/0);
- userMapper.insert(user2);
- }
- }
然后在我们service的新建方法中,我们两次调用userMapper的insert方法,也就是说进行了两次数据库的插入操作。并且在中间人为制造了一个异常。
先来看没进行事务管理的效果:
测试类:
- @RunWith(SpringRunner.class) // SpringJUnit支持,由此引入Spring-Test框架支持!
- @SpringBootTest //提供spring环境
- public class TransactionTest {
- @Autowired
- private UserService userService;
- @Test
- public void testTransaction() {
- User user1 = new User();
- User user2 = new User();
- user1.setId("111111");
- user1.setUserAccount("tt");
- user1.setUserName("user1");
- user1.setUserPassword("12312312");
- user2.setId("222222");
- user2.setUserAccount("bb");
- user2.setUserName("user2");
- user2.setUserPassword("12312312");
- userService.transactionTest(user1, user2);
- }
- }
结果截图:
运行前的数据:
运行后:
可见,执行了一个insert之后,因为异常,后面那个insert就没执行了。
好的现在我们加入事务管理:
加入启动事务管理注解:
- @SpringBootApplication(scanBasePackages={"com.stuPayment.*"})
- @EnableTransactionManagement
- @MapperScan("com.stuPayment.dao")
- public class StuPaymentApplication {
- public static void main(String[] args) {
- SpringApplication.run(StuPaymentApplication.class, args);
- }
- }
加入Transactional注解:
可以加在类上,这样的话,就类中的所有方法都事务管理,这里我们加在方法上:
- @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED, rollbackFor=Exception.class)
- @Override
- public void transactionTest(User user1, User user2) {
- // TODO Auto-generated method stub
- userMapper.insert(user1);
- System.out.println(1/0);
- userMapper.insert(user2);
- }
再运行看效果:
因为这个时候是REQUIRED的传播级别,也就是说两个方法都是一个事务的,只要有错误,就会回滚。
最后再说几点注意的地方:
1、用 spring 事务管理器,由spring来负责数据库的打开,提交,回滚.默认遇到运行期例外(throw new RuntimeException("注释");)会回滚,即遇到不受检查(unchecked)的例外时回滚;而遇到需要捕获的例外(throw new Exception("注释");)不会回滚,即遇到受检查的例外(就是非运行时抛出的异常,编译器会检查到的异常叫受检查例外或说受检查异常)时,需我们指定方式来让事务回滚要想所有异常都回滚,要加上 @Transactional( rollbackFor={Exception.class,其它异常}) .如果让unchecked例外不回滚: @Transactional(notRollbackFor=RunTimeException.class)
2、@Transactional 注解应该只被应用到 public 可见度的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,它也不会报错, 但是这个被注解的方法将不会展示已配置的事务设置。
3、Spring团队的建议是你在具体的类(或类的方法)上使用 @Transactional 注解,而不要使用在类所要实现的任何接口上。你当然可以在接口上使用 @Transactional 注解,但是这将只能当你设置了基于接口的代理时它才生效。因为注解是不能继承的,这就意味着如果你正在使用基于类的代理时,那么事务的设置将不能被基于类的代理所识别,而且对象也将不会被事务代理所包装(将被确认为严重的)。因此,请接受Spring团队的建议并且在具体的类上使用 @Transactional 注解。
参考过的博客:
这两个是介绍Spring的事务管理的综述的
https://www.cnblogs.com/yixianyixian/p/8372832.html
https://blog.csdn.net/donggua3694857/article/details/69858827
这两个是介绍传播行为的概念的,第一个讲概念可以,第二个的例子挺清晰:
https://blog.csdn.net/wwh578867817/article/details/51736723
https://blog.csdn.net/hsgao_water/article/details/52860380
SpringBoot中的事务管理:
https://blog.csdn.net/wohaqiyi/article/details/72895983
@Transactional注解介绍:
https://www.cnblogs.com/caoyc/p/5632963.html
Springboot下事务管理的简单使用的更多相关文章
- springboot mybatis 事务管理
本文主要讲述springboot提供的声明式的事务管理机制. 一.一些概念 声明式的事务管理是基于AOP的,在springboot中可以通过@Transactional注解的方式获得支持,这种方式的优 ...
- 架构设计 | 基于Seata中间件,微服务模式下事务管理
源码地址:GitHub·点这里 || GitEE·点这里 一.Seata简介 1.Seata组件 Seata是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务.Seata将为用 ...
- springboot开启事务管理
spring中开启事务管理需要在xml配置文件中配置,springboot中采取java config的配置方式. 核心是@EnableTransactionManager注解,该注解即为开启事务管理 ...
- spring boot学习(6) SpringBoot 之事务管理
两个操作要么同时成功,要么同时失败: 事务的一致性: 以前学ssh ssm都有事务管理service层通过applicationContext.xml配置,所有service方法都加上事务操作: 用来 ...
- SpringBoot设置事务管理
关于事务就不介绍了,前面在研究spring的时候就已经研究过了,参考:https://www.cnblogs.com/qlqwjy/p/7296493.html 这里直接研究springboot中事务 ...
- ## springboot 下策略模式的简单使用
1.灵魂三问 接手前人(已跑路)项目快乐否? 前人项目不写注释懵逼否? 一个方法中一堆if/else,且业务判断条件用简单数字(或英文字母),不带注释,想打人否? 所以,对于上述三个问题,我写 ...
- springboot下使用dubbo的简单demo
1.一些话 现在java后端开发大多用springboot来简化环境搭建,现在一直使用的是springcloud和k8s有关的东西,以前用过dubbo,但那会儿的开发环境搭建流程较为繁琐,而且不支持r ...
- Spring基于AOP的事务管理
Spring基于AOP的事务管理 事务 事务是一系列动作,这一系列动作综合在一起组成一个完整的工作单元,如果有任何一个动作执行失败,那么事务 ...
- (转)Hibernate事务管理
Hibernate的事务管理 事务(Transaction)是工作中的基本逻辑单位,可以用于确保数据库能够被正确修改,避免数据只修改了一部分而导致数据不完整,或者在修改时受到用户干扰.作为一名软件设计 ...
随机推荐
- git rev-list 和 git rev-parse
git-rev-list - Lists commit objects in reverse chronological order https://git-scm.com/docs/git-rev- ...
- 旋转屏幕导致Activity重建
简单来说,Activity是负责与用户交互的最主要机制,任何“设置”(Configuration)的改变都可能对Activity的界面造成影响,这时系统会销毁并重建Activity以便反映新的Conf ...
- C语言中数组做函数参数的问题
数组做函数参数,会退化成为一个指针变量.因此在进行数组参数传递的同时,需要传递一个数组长度的参数变量. 数组长度可以通过sizeof(arr)/siezof(arr[0])来得到.关于这个sizeof ...
- SVN与CVS比较-怎度网
SVN与CVS比较 所有的文档都显示SVN可以取代CVS,同时SVN的问题和缺点都被隐藏了.不幸的是,我们并不认为SVN是CVS的替代品,尽管很多缺陷都被修改了.更有甚者,它甚至让人重回VSS.CVS ...
- python 基础之第二天
[root@master script]# vim while_counter.py #!/usr/bin/python # coding: utf-8 sum = 0 counter = 0 whi ...
- 「网络流24题」「LuoguP3358」 最长k可重区间集问题(费用流
题目描述 对于给定的开区间集合 I 和正整数 k,计算开区间集合 I 的最长 k可重区间集的长度. 输入输出格式 输入格式: 的第 1 行有 2 个正整数 n和 k,分别表示开区间的个数和开区间的可重 ...
- MongoDB 复制集节点增加移除及节点属性配置
复制集(replica Set)或者副本集是MongoDB的核心高可用特性之一,它基于主节点的oplog日志持续传送到辅助节点,并重放得以实现主从节点一致.再结合心跳机制,当感知到主节点不可访问或宕机 ...
- 基于WinDbg的内存泄漏分析
在前面C++中基于Crt的内存泄漏检测一文中提到的方法已经可以解决我们的大部分内存泄露问题了,但是该方法是有前提的,那就是一定要有源代码,而且还只能是Debug版本调试模式下.实际上很多时候我们的程序 ...
- 将DotNetBar添加到工具箱中
一.首先得安装DotNetBar; 二.在工具箱的空白处右击=====>添加选项卡(图一)======>命名为DotNetBar(图二) 图一 图二 三.右击DotNetBar====== ...
- 写出高效优美的单片机C语言代码
程序能跑起来并不见得你的代码就是很好的c代码了,衡量代码的好坏应该从以下几个方面来看 1,代码稳定,没有隐患. 2,执行效率高. 3,可读性高. 4,便于移植. 下面发一些我在网上看到的技巧和自己的一 ...