原文:
https://blog.csdn.net/soonfly/article/details/70305683
https://www.cnblogs.com/dennyzhangdd/p/9602670.html
https://blog.csdn.net/fly910905/article/details/80000242
说起Spring的事务,仿佛是很熟悉的老朋友了,然鹅。。。现实却很残酷。。。起初我以 Spring、Mybatis、druid、Mysql尝试,发现其中一个问题,无论数据源的defaultAutoCommit设置为true或者false,事务总会自动提交。确定配置无误后,发现网上有一种说法是把数据库的autocommit设置为OFF,即关闭数据库的自动提交,且不说这样是否可以,这种方法本身就有问题。因为在代码中获取到一个Connection,执行commit即可提交,不执行commit就不会提交,完全可以由代码控制,去设置数据库本身,这很不合理。经过一番周折还是没有搞定这个问题。
无奈之下我把Mybatis换成JdbcTemplate,终于正常了。下面基于Spring+JdbcTemplate+druid+Mysql说说事务。
1、事务、事务传播机制的简单说明
事务是一个单体行为,只有提交了事务,数据才会保存到数据库,否则不会保存到数据库中。事务传播行要求至少有两个东西,才可以发生传播。指的是当一个事务方法被另一个事务方法调用时,这个被调用方法的事务方法应该如何进行。例如:methodA事务方法调用methodB事务方法时,methodB是继续在调用者methodA的事务中运行呢,还是为自己开启一个新事务运行,这就是由methodB的事务传播行为决定的。
2、defaultAutoCommit与Transactional的关系
配置数据源时参数defaultAutoCommit设置为ture、false代表自动、不自动提交。Transactional注解也控制事务,他们有什么关系?下面用例子说明。
(1)defaultAutoCommit为false,不使用Transactional注解。结论:不会提交
<property name="defaultAutoCommit" value="false" />
public void save() { testDao.save(); }
(2)defaultAutoCommit为false,使用Transactional注解。结论:会提交
<property name="defaultAutoCommit" value="false" />
@Transactional
public void save() { testDao.save(); }
(3)defaultAutoCommit为true,不使用Transactional注解。结论:会提交
<property name="defaultAutoCommit" value="true" />
public void save() { testDao.save(); }
(4)defaultAutoCommit为true,使用Transactional注解。结论:会提交
<property name="defaultAutoCommit" value="true" />
@Transactional public void save() { testDao.save();
}
总结:只要defaultAutoCommit或者Transactional有一项设置为可提交即可。
3、Transactional与异常自动回滚的关系
在项目中希望当方法产生异常时自动回滚事务,下面我们在defaultAutoCommit设置为false的情况下进行验证
<property name="defaultAutoCommit" value="false" />
(1)使用Transactional的默认配置,抛出检查型异常。事务不会回滚
@Transactional public void save () throws Exception { testDao.save(); throw new ClassNotFoundException(); }
(2)使用Transactional的默认配置,抛出运行时异常。事务会回滚
@Transactional public void save (){ testDao.save(); throw new RuntimeException(); }
(3)使用Transactional注解,指定rollbackFor为抛出的异常或其父类时,检查型异常会回滚
@Transactional(rollbackFor=Exception.class) public void save () throws Exception {
testDao.save(); throw new ClassNotFoundException(); }
(4)使用Transactional注解,指定rollbackFor不是抛出的异常或其父类时,运行时异常会回滚(运行时异常与rollbackFor无关,肯定回滚)
@Transactional(rollbackFor=FileNotFoundException.class) public void save () throws Exception { testDao.save(); throw new RuntimeException(); }
(5)使用Transactional注解,捕获异常,事务不会回滚
@Transactional public void save () throws Exception { try { testDao.save(); throw new RuntimeException(); } catch (Exception e) { // TODO: handle exception } }
@Transactional public void save () throws Exception { try { testDao.save(); throw new ClassNotFoundException(); } catch (Exception e) { // TODO: handle exception }
}
(6)捕获的异常需要手动回滚,手动回滚时检查型异常可以不指定rollbackFor
@Transactional public void save () { try { testDao.save(); throw new ClassNotFoundException(); } catch (Exception e) { TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } }
@Transactional(rollbackFor=FileNotFoundException.class) public void save () { try { testDao.save(); throw new ClassNotFoundException(); } catch (Exception e) { TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } }
@Transactional public void save () { try { testDao.save(); throw new RuntimeException(); } catch (Exception e) { TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } }
(7) Transactional要加在主动(直接)调用的方法上面,以下事务不会提交,没有开启事务(spring容器管理的类直接调用test)
public void test(){ save(); } @Transactional public void save () { try { testDao.save(); throw new RuntimeException(); } catch (Exception e) { } }
4、spring中的事务传播行为
spring*有7种事务传播行为,分别介绍如下:
(1)PROPAGATION_REQUIRED:如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。
方法A加注解,方法B也加注解,当方法A运行时会开启事务A,调用方法B时,方法B也加入到事务A中
@Transactional(propagation = Propagation.REQUIRED) public void methodA() { methodB(); testDao.methodA(); } @Transactional(propagation = Propagation.REQUIRED) public void methodB() { testDao.methodB(); }
如上图,总共开启了一个事务。
(2)PROPAGATION_SUPPORTS:如果存在一个事务,支持当前事务,如果没有事务,则不会开启事务。
@Transactional(propagation = Propagation.REQUIRED) public void methodA() { methodB(); testDao.methodA(); } @Transactional(propagation = Propagation.SUPPORTS) public void methodB() { testDao.methodB(); }
如果直接调用methodA,methodA会开启一个事务,methodA调用methodB,则methodB支持当前methodA开启的事务,如下图:
如果直接调用methodB,不会开启事务,如下图:
如果直接调用methodA,由于methodA是SUPPORTS,不会开始事务,methodB不是直接调用,也不会开启事务
@Transactional(propagation = Propagation.SUPPORTS) public void methodA() { methodB(); testDao.methodA();
} @Transactional(propagation = Propagation.REQUIRED) public void methodB() { testDao.methodB(); }
(3)PROPAGATION_MANDATORY:必须在一个事务中运行,否则报异常
@Transactional(propagation = Propagation.REQUIRED) public void methodA() { methodB(); testDao.methodA(); }
@Transactional(propagation = Propagation.MANDATORY) public void methodB() { testDao.methodB(); }
直接调用methodA,开启一个事务,methodB也在该事务中运行
直接调用methodB,报异常 No existing transaction found for transaction marked with propagation 'mandatory'
@Transactional(propagation = Propagation.MANDATORY) public void methodB() { testDao.methodB(); }
(4)PROPAGATION_REQUIRES_NEW:开启一个新事务。如果一个事务已经存在,则先将存在的事务挂起,执行完这个新事务,再执行挂起的事务,两个事务的成功或失败没有联系。
@Transactional(propagation = Propagation.REQUIRED) public void methodA() { methodB(); testDao.methodA();
}
@Transactional(propagation = Propagation.REQUIRES_NEW) public void methodB() { testDao.methodB(); }
从上图中看到,并没有挂起旧事务,先执行新事务,因为只有使用JtaTransactionManager作为事务管理器时才生效。后面再研究。。。
(5)PROPAGATION_NOT_SUPPORTED:在非事务中运行。如果有事务正在运行,他将在运行期被挂起,直到这个事务提交或者回滚才恢复执行。
@Transactional(propagation = Propagation.REQUIRED) public void methodA() { methodB(); testDao.methodA(); } @Transactional(propagation = Propagation.NOT_SUPPORTED) public void methodB() { testDao.methodB(); }
直接调用methodA,运行到methodB,事务应该挂起,即methodB对应的数据不会保存到数据库。
但上图与预期的不一致,因为也需要JtaTransactionManager作为事务管理器 。
直接调用methodB不会开启事务,可以自己尝试一下。
(6)PROPAGATION_NEVER:总是非事务地执行,如果存在一个活动事务,则抛出异常。
@Transactional(propagation = Propagation.NEVER) public void methodB() { testDao.methodB(); }
直接调用methodB,不会开启事务
@Transactional(propagation = Propagation.REQUIRED) public void methodA() { methodB(); testDao.methodA(); } @Transactional(propagation = Propagation.NEVER) public void methodB() { testDao.methodB(); }
直接调用methodA,报异常,发现下面日志没有报异常,,,是版本问题还是我的理解有误???先留个疑问吧
(7) PROPAGATION_NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中。 如果没有活动事务, 则按PROPAGATION_REQUIRED属性执行。