关于@Transactionsal注解
一 . 关于事务
1 .事务的特性:
1) 原子性(Atomicity) 事务是数据库的逻辑工作单位,它对数据库的修改要么全部执行,要么全部不执行。
2) 一致性(Consistemcy) 事务前后,数据库的状态都满足所有的完整性约束。
3) 隔离性(Isolation) 并发执行的事务是隔离的,一个不影响一个。通过设置数据库的隔离级别,可以达到不同的隔离效果
4) 持久性(Durability) 在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。
2 .@Transactional注解的配置项:
@Transactional(propagation=Propagation.REQUIRED) //控制事务传播。默认是Propagation.REQUIRED
@Transactional(isolation=Isolation.DEFAULT) //控制事务隔离级别。默认跟数据库的隔离级别相同
@Transactional(readOnly=false) //控制事务可读写、只可读。默认可读写
@Transactional(timeout=30) //控制事务的超时时间,单位秒。默认跟数据库的事务控制系统相同
@Transactional(rollbackFor=RuntimeException.class) //控制事务遇到哪些异常会回滚。默认是RuntimeException
@Transactional(rollbackForClassName=RuntimeException) //同上
@Transactional(noRollbackFor=NullPointerException.class) //控制事务遇到哪些异常不会回滚。默认遇到RuntimeException回滚
@Transactional(noRollbackForClassName=NullPointerException)//同上
3 .事务的7种传播特性:
1) REQUIRED 如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。
2) SUPPORTS 如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行
3) MANDATORY 如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。
4) NESTED 如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务,则按REQUIRED属性执行
5) NEVER 总是非事务地执行,如果存在一个活动事务,则抛出异常
6) REQUIRES_NEW 总是开启一个新的事务。如果一个事务已经存在,则将这个已经存在的事务挂起
7) NOT_SUPPORTED 总是非事务地执行,并挂起任何存在的事务
二 . 关于方法调用引起的事务
1. 方法a()调用带有事务的方法b()
方法a()没有事务 | 方法a()有事务 | |
REQUIRED | b()创建自己的事务 | b()接受a()的事务 |
SUPPORTS | b()不创建自己的事务 | b()接受a()的事务 |
MANDATORY | b()报异常 | b()接受a()的事务 |
NESTED | b()创建自己的事务 | b()接受a()的事务,成为a()嵌套的子事务 |
NEVER | b()不创建自己的事务 | b()报异常 |
REQUIRES_NEW | b()创建自己的事务 | b()不接受a()的事务,b()先执行,内层事务失败不会影响外层事务 |
NOT_SUPPORTED | b()不创建自己的事务 | b()不接受a()的事务,b()先执行 |
以上详细内容参考:https://www.cnblogs.com/lukelook/p/11246180.html
三 . 使用@Transactional注解,在方法中抛出异常时,回滚不生效
1. 可能的原因:
1) 数据库引擎要支持事务
如果是MySQL,注意表要使用支持事务的引擎,比如innodb,如果是myisam,事务是不起作用的
2) 是否开启了对注解的解析
配置文件必须加<tx:annotation-driven />,否则不解析@Transactional
2. 解决办法:
1) 如果是MySQL数据库,查看引擎是否是支持事务的引擎
2) 抛出的异常是否是注解的回滚异常,默认是RuntimeException或者Error
在业务代码中,有如下两种情况,比如:
throw new RuntimeException("xxxxxxxxxxxx"); 事务回滚
throw new Exception("xxxxxxxxxxxx"); 事务没有回滚
此时需要将@Transactional注解的rollbackFor参数赋值为Exception.class(或者某个特定异常的class),此时将支持该异常回滚
3) 如果使用try-catch捕获抛出的unchecked异常后没有在catch块中采用页面硬编码的方式使用spring api对事务做显式的回滚,则事务不会回滚,
“将异常捕获,并且在catch块中不对事务做显式提交=生吞掉异常”,如果非要catch,在做完你想做的工作后一定要抛出可引起回滚的异常,否则spring会将你的操作commit
3. 针对是否捕获异常那么现在有两个情况:
情况1:如果没有在程序中手动捕获异常
1 @Transactional(rollbackFor = { Exception.class }) 2 public void test() throws Exception { 3 doDbStuff1(); 4 doDbStuff2(); 5 //假如这个操作数据库的方法会抛出异常,现在方法doDbStuff1()对数据库的操作会回滚。 6 }
情况2:如果在程序中自己捕获了异常
1 @Transactional(rollbackFor = { Exception.class }) 2 public void test() { 3 try { 4 doDbStuff1(); 5 doDbStuff2();//假如这个操作数据库的方法会抛出异常,现在方法doDbStuff1()对数据库的操作 不会回滚。 6 } catch (Exception e) { 7 e.printStackTrace(); 8 } 9 }
现在如果我们需要手动捕获异常,并且也希望抛异常的时候能回滚肿么办呢?
方法一:
1 @Transactional(rollbackFor = { Exception.class }) 2 public void test() { 3 try { 4 doDbStuff1(); 5 doDbStuff2(); 6 } catch (Exception e) { 7 e.printStackTrace(); 8 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();//就是这一句了,加上之后,如果doDbStuff2()抛了异常, //doDbStuff1()是会回滚的 9 } 10 }
以上内容详细参考:https://blog.csdn.net/qq_24084925/article/details/53116846
需要注意的是:在方法一中,使用的 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); 会将方法事务设置为只回滚,无法commit
如果想要实现正常的回滚和提交,可以使用 savepoint 方式:
1 @Transactional(rollbackFor = { Exception.class }) 2 public void test() { 3 Object savepoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint(); 4 try { 5 doDbStuff1(); 6 doDbStuff2(); 7 } catch (Exception e) { 8 e.printStackTrace(); 9 TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savepoint);//如果抛了异常,会回滚到savepoint 10 } 11 }
方法二:
在捕获异常后,再抛出一个可以被事务识别的异常,使其回滚
1 @Transactional(rollbackFor = { Exception.class }) 2 public void test() { 3 try { 4 doDbStuff1(); 5 doDbStuff2(); 6 } catch (Exception e) { 7 e.printStackTrace(); 8 throw new Exception(); 9 } 10 }
需要注意的是:此种方法在捕获后进行的操作,只有在回滚后不影响其效果时使用,如,打印日志操作,回滚后不会将已经打印好的日志删除
4 . spring动态代理为事务埋下的坑
描述:spring中的事务控制是通过动态代理实现的, 在一个Service内部,事务方法之间的嵌套调用,普通方法和事务方法之间的嵌套调用,都不会开启新的事务.由于spring采用动态代理机制来实现事务控制,而动态代理最终都是要调用原始对象的,而原始对象在去调用方法时,是不会再触发代理了!通俗点讲就是一个类的AOP动态代理,只有在其方法被其他类中的方法调用时才会触发(使用触发是否恰当留待后续
),后续的通过本类的方法调用本类的方法便不会再次触发动态代理,也就无法再创建事务
解决办法:
办法一:可以把方法B放到另外一个service或者dao,然后把这个server或者dao通过@Autowired注入到方法A的bean里面,这样即使方法A没用事务,方法B也可以执行自己的事务了。(将方法b(抽离到其他bean中,这样在调用时会重新触发动态代理))
办法二:通过AOP代理调用b方法即可走事务切面
1 public class TestService{ 2 public void a() { 3 TestService aopProxy = (TestService)AopContext.currentProxy(); 4 aopProxy.b();//即调用AOP代理对象的b方法即可执行事务切面进行事务增强 5 } 6 }
详情参考:https://www.iteye.com/topic/35907/
https://www.cnblogs.com/sung1024/archive/2004/01/13/11700449.html
如何获取代理对象,可以参考:
https://blog.csdn.net/dapinxiaohuo/article/details/52092447
每天一点点