SpringBoot显式事务
参考:https://www.jianshu.com/p/f5fc14bde8a0
后续测试代码的完整项目:https://files.cnblogs.com/files/hellohello/demo2.rar
后续说的事务注解都是指 import javax.transaction.Transactional;事务注解如果修饰在类上,则等价与作用在这个类的所有方法上,如果仅修饰在函数上,则仅仅作用在这个函数上,对其他函数没有效果。
- 只要加了事务注解,不管是加到bean上,还是加到bean中的函数上,spring才会生成一个代理对象与对应的bean,共两个对象。
- 注入到容器中的是代理对象,而不是被代理的bean对象。
- 代理对象实际上是被代理对象的子类,通过CGLib动态生成的。
- 其他bean中注入的虽然是代理对象,但是对于开发人员来说,就跟直接调用实际的bean对象一样,是透明的,因为代理对象内部会调用被代理对象的对应函数。
- 代理对象调用被代理对象函数时,只有抛出了uncheck exception(RuntimeException或Error)时【或配置其他异常】,并且抛出异常的函数处于事务注解的作用范围内时,事务才会回滚。
测试1、2、3点:
服务类代码,成员函数上存在事务注解,所以会生成代理对象
@Service
public class ConfSystemService {
// 用于记录被代理对象的引用
public static ConfSystemService ins ;
public ConfSystemService(){
ins = this;
}
@Transactional
public void saveWithOk(int id){
// ...
}
}
测试类中进行测试
@Autowired
ConfSystemService confSystemService;
@Test
public void test3(){
Assert.isTrue(!confSystemService.equals(ConfSystemService.ins),"判断是否属于同一个对象");
Assert.isTrue(confSystemService.getClass().getSuperclass().equals(ConfSystemService.class),"判断是否是父类子类关系");
Assert.isTrue(!ConfSystemService.ins.getClass().getSuperclass().equals(ConfSystemService.class),"判断是否是父类子类关系");
}
如果把服务类中的事务注解去掉,则不会生成代理对象,那上面例子中的静态属性保存的对象,跟其他地方注册的服务对象,就是同一个对象了。
测试4、5点:
在服务类中添加如下代码:
/**
* dao.
*/
@Autowired
ConfSystemRepository confSystemRepository;
/**
* 使用dao保存数据后,报错.
* @param id .
*/
@Transactional
public void saveWithErr(int id){
ConfSystem confSystem = new ConfSystem();
confSystem.setConfigurename("123");
confSystem.setConfigurevalue("456");
confSystem.setConfsystemid(id);
confSystemRepository.save(confSystem);
throw new RuntimeException("模拟错误");
}
/**
* 正常保存数据.
* @param id .
*/
@Transactional
public void saveWithOk(int id){
ConfSystem confSystem = new ConfSystem();
confSystem.setConfigurename("123");
confSystem.setConfigurevalue("456");
confSystem.setConfsystemid(id);
confSystemRepository.save(confSystem);
}
/**
* 判断数据是否成功插入到了数据库中.
* @param id .
* @return .
*/
public boolean checkExist(int id){
return confSystemRepository.findById(id).isPresent();
}
执行单元测试
@Test
public void test1() {
try{
confSystemService.saveWithErr(888);
}catch (Exception e){
e.printStackTrace();
}
Assert.isTrue(!confSystemService.checkExist(888));
}
@Test
public void test2() {
confSystemService.saveWithOk(999);
Assert.isTrue(confSystemService.checkExist(999));
}
第一个单元测试中,模拟了一个报错,事务就回滚了,所以检查出数据不存在。其中实际调用了代理类的saveWithErr方法,而这个方法中调用了被代理对象的saveWithErr方法,而这个方法中抛出了一个RuntimeException,这个异常被代理类的saveWithErr方法检测到,而且代理类发现当前方法处于事务注解的作用下,所以代理类就会将事务回滚,最后再将这个RuntimeException抛出去,让调用者知道这个方法报错了。
事务没有回滚
以上提到了回滚的两个必要条件:
- 当前方法处于事务注解的作用范围内
- 方法得抛出RuntimeException或Error的子异常,或配置的指定异常
测试第1条,场景:服务类中未出于事务注解作用下的方法调用了,处于事务注解作用下的方法。服务类中添加如下代码:
public void funcWithNoTrans(int id){
saveWithErr(id);
}
测试类中添加如下测试用例(和之前的test1很类似,至不要调用的服务方法不一样):
@Test
public void test5() {
try{
confSystemService.funcWithNoTrans(888);
}catch (Exception e){
e.printStackTrace();
}
Assert.isTrue(!confSystemService.checkExist(888));
}
这个测试用例无法通过。是因为代理类执行当前方法时,虽然执行的被代理对象的方法中抛出了异常,但是代理类发现当前方法(funcWithNoTrans)并不是处于事务注解作用下,所以事务没有回滚。
测试第2条,场景:没有抛出uncheck exception,而是抛出了自定义的异常,同时没有做对应配置,在服务类中添加代码:
// 自定义异常
public static class MyException extends Exception {
// ...
}
// 没有配置
@Transactional
public void saveWithCustomException(int id) throws MyException {
ConfSystem confSystem = new ConfSystem();
confSystem.setConfigurename("123");
confSystem.setConfigurevalue("456");
confSystem.setConfsystemid(id);
confSystemRepository.save(confSystem);
throw new MyException();
}
// 有配置
@Transactional(rollbackOn = MyException.class)
public void saveWithCustomCfgException(int id) throws MyException {
ConfSystem confSystem = new ConfSystem();
confSystem.setConfigurename("123");
confSystem.setConfigurevalue("456");
confSystem.setConfsystemid(id);
confSystemRepository.save(confSystem);
throw new MyException();
}
测试代码:
@Test
public void test6() {
try{
confSystemService.saveWithCustomCfgException(111);
}catch (Exception e){
e.printStackTrace();
}
Assert.isTrue(!confSystemService.checkExist(111),"抛出已配置的自定义异常");
}
@Test
public void test7() {
try{
confSystemService.saveWithCustomException(222);
}catch (Exception e){
e.printStackTrace();
}
Assert.isTrue(confSystemService.checkExist(222),"抛出未配置的自定义异常");
}
两个测试都能通过。
还有另外一个场景:服务类内部出现异常了,但是内部try...catch处理掉了,导致代理类检测不出来被代理对象内部其实出现了异常,最终事务也没有回滚。
ps:测试发现,换用另一个spring包下的Transactional注解,具有以上相同的效果,只不过配置注解那里,得改成 rollbackFor。这两个注解功能上其实没啥区别,只是配置的属性名有点差异而已。
其他
被注解修饰的函数是运行在一个事务内,所以要保证这个函数运行的时间要尽可能短(如不要穿插网络请求,实在不行的话,则将网络请求剥离到这个事务方法之外,这样就不影响这个方法的执行时间了)。而且在这个函数内,该抛的运行时异常要抛出来,而不要trycatch住,否则导致事务无法回滚
事务注解,加到service方法上,service方法内调用不同的dao来操作数据。一个sevice方法内有两个数据库修改操作以上,才需要在这个service方法上加事务注解
SpringBoot显式事务的更多相关文章
- SQL Server显式事务与隐式事务
事务是单个的工作单元.如果某一事务成功,则在该事务中进行的所有数据修改均会提交,成为数据库中的永久组成部分.如果事务遇到错误且必须取消或回滚,则所有数据库修改均被清除. SQL Server中有一下几 ...
- SQLServer之创建显式事务
显式事务定义 显式事务以 BEGIN TRANSACTION 语句开始,并以 COMMIT 或 ROLLBACK 语句结束. 备注 BEGIN TRANSACTION 使 @@TRANCOUNT 按 ...
- 基于 Transaction 类的分布式显式事务
自.NET2.0以来增加了System.Transactions命名空间,为.NET应用程序带来了一个新的事务编程模型. 这个命名空间提供了几个依赖的TransactionXXX类.Transacti ...
- (4.19)sql server中的事务模式(隐式事务,显式事务,自动提交事务)
(4.19)sql server中的事务模式(隐式事务,显式事务,自动提交事务) 1.概念:隐式事务,显式事务,自动提交事务 2.操作:如何设置事务模式 3.存储过程中的事务 XACT_ABORT 1 ...
- SQL Server Insert时开启显式事务
如果没法避免一条一条的写入,那么在处理前显示开启一个事务 begin tran 在处理完成后 commit 这样也要比不开显示事务会快很多! while i < 10000begin inse ...
- CALayer的隐式动画和显式动画
隐式事务 任何对于CALayer属性的修改,都是隐式事务,都会有动画效果.这样的事务会在run-loop中被提交. - (void)viewDidLoad { //初始化一个layer,添加到主视图 ...
- springBoot service层 事务控制
springBoot使用事物比较简单,在Application启动类s上添加@EnableTransactionManagement注解,然后在service层的方法上添加@Transactional ...
- 当 IDENTITY_INSERT 设置为 OFF 时,不能为表‘XXX’中的标识列插入显式值。
在创建事务复制时,很多时候不一定使用快照进行初始化,而是使用备份还原初始化.当对有标识列(即identity的自增列)的表进行复制的时候,使用备份还原初始化搭建起来的复制常常就会报错,即:当 IDEN ...
- CoreAnimation4-隐式动画和显式动画
事务 Core Animation基于一个假设,说屏幕上的任何东西都可以(或者可能)做动画.动画并不需要你在Core Animation中手动打开,相反需要明确地关闭,否则他会一直存在. 当你改变CA ...
随机推荐
- python学习一(Python中的列表)
python中有两种列表,分别用()和[]表示: 例如: letter = ('a','b','c') letter = ['a','b','c'] 用小括号表示的列表初始化后不允许修改,而中中括号生 ...
- 深入理解C#中的IDisposable接口(转)
转自:https://www.cnblogs.com/wyt007/p/9304564.html 写在前面 在开始之前,我们需要明确什么是C#(或者说.NET)中的资源,打码的时候我们经常说释放资源, ...
- https微信分享看不到图片的坑
最近在做一个活动项目的时候一开始走的http,发现网络被劫持的特别严重,没办法,只能改走https,但是修改为https后发现在使用微信js-sdk分享的时候看不到缩略图,直接通过地址打开是可以找开图 ...
- NGSL + NAWL 单词表 以及学习网站
https://quizlet.com/44769538/nawl-1-1-50-flash-cards/ NAWL 网站 NAWL 单词表 + NGSL 单词表 http://www.newgen ...
- OpenCV之cvAddWeighted直接C语言实现版addWeighted,应对上下平滑融合拼接
关于OpenCV中的cvAddWeighted的介绍可参见<opencv中的cvAddWeighted函数> cvAddWeighted有个问题,它只能实现两张图片的直接融合,往往产生明显 ...
- Qt安装教程
一.Qt下载 官网下载链接http://download.qt.io/archive/qt/,下载最新版 5.10 官网的下载网站有的时候可能会抽风,也可以选择国内的镜像下载源http://mirro ...
- vs移动团队项目集合
vs移动团队项目集合: https://msdn.microsoft.com/zh-cn/library/vs/alm/dd936138(v=vs.120)/css
- Mysql数据库学习总结(一)
数据库概念 数据库(Database)是按照数据结构来组织.存储和管理数据,建立在计算机存储设备上的仓库. 简单说,数据库就是存放数据的仓库.和图书馆存放书籍.粮仓存放粮食类似. 数据库分类 分为 关 ...
- 一个普通 iOS 码农的几个小项目相关知识点总结
题记:在开发的路途上,有的人走的很深很远,而对于停留在初级阶段的我来说,还要学的.经历的还有很多... list sqlite 数据库中,当把表里的数据都清空时,下次插入的数据的 id 主键不会从 0 ...
- JavaScript模块化开发的那些事
模块化开发在编程开发中是一个非常重要的概念,一个优秀的模块化项目的后期维护成本可以大大降低.本文主要介绍了JavaScript模块化开发的那些事,文中通过一个小故事比较直观地阐述了模块化开发的过程. ...