Spring事务失效问题分析及解决方案
这篇文章主要介绍了spring事务失效问题分析及解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
隔离级别
在 transactiondefinition.java 接口中,定义了“四种”的隔离级别枚举:
/** * 【spring 独有】使用后端数据库默认的隔离级别 * * mysql 默认采用的 repeatable_read隔离级别 * oracle 默认采用的 read_committed隔离级别 */ int isolation_default = -1; /** * 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读 */ int isolation_read_uncommitted = connection.transaction_read_uncommitted; /** * 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生 */ int isolation_read_committed = connection.transaction_read_committed; /** * 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。 */ int isolation_repeatable_read = connection.transaction_repeatable_read; /** * 最高的隔离级别,完全服从acid的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。 * * 但是这将严重影响程序的性能。通常情况下也不会用到该级别。 */ int isolation_serializable = connection.transaction_serializable;
事务的传播级别
事务的传播行为,指的是当前带有事务配置的方法,需要怎么处理事务;例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行;
需要注意,事务的传播级别,并不是数据库事务规范中的名词,而是 spring 自身所定义的。通过事务的传播级别,spring 才知道如何处理事务,是创建一个新事务呢,还是继续使用当前的事务;
在 transactiondefinition.java 接口中,定义了三类七种传播级别:
// ========== 支持当前事务的情况 ========== /** * 如果当前存在事务,则使用该事务。 * 如果当前没有事务,则创建一个新的事务。 */ int propagation_required = 0; /** * 如果当前存在事务,则使用该事务。 * 如果当前没有事务,则以非事务的方式继续运行。 */ int propagation_supports = 1; /** * 如果当前存在事务,则使用该事务。 * 如果当前没有事务,则抛出异常。 */ int propagation_mandatory = 2; // ========== 不支持当前事务的情况 ========== /** * 创建一个新的事务。 * 如果当前存在事务,则把当前事务挂起。 */ int propagation_requires_new = 3; /** * 以非事务方式运行。 * 如果当前存在事务,则把当前事务挂起。 */ int propagation_not_supported = 4; /** * 以非事务方式运行。 * 如果当前存在事务,则抛出异常。 */ int propagation_never = 5; // ========== 其他情况 ========== /** * 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行。 * 如果当前没有事务,则等价于 {@link transactiondefinition#propagation_required} */ int propagation_nested = 6;
@transaction
@transaction 注解是spring的tx模块提供的,使用aop实现的事务控制,即底层为动态代理;
@transaction 可以作用于接口、接口方法、类以及类方法上;当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,也可以在方法级别使用该标注来覆盖类级别的定义;
事务失效
1.异常类型错误,默认是runtimexception才会回滚
2.异常被catch后没有抛出,需要抛异常才能回滚
3.是否发生自身调用的问题
4.注解所在位置是否为public修饰
5.数据源没有配置事务管理器
6.不支持事务的引擎,如myisam
下面是一个事务失效的例子
@slf4j @service public class orderserviceimpl implements orderservice { @autowired private ordermapper ordermapper; @transactional @override public void parent() { try { this.child(); } catch (exception e) { log.error("插入异常", e); } order order = new order(); order.setorderno("parent"); order.setstatus("0"); order.settitle("parent"); order.setamount("1000"); ordermapper.insert(order); } @transactional @override public void child() { order order = new order(); order.setorderno("child"); order.setstatus("0"); order.settitle("child"); order.setamount("2000"); ordermapper.insert(order); throw new runtimeexception(); } }
当调用parent方法时,会调用child方法,但执行完成后插入的记录有两条,一条是parent的,一条是child的;
这是因为上面的parent方法调用的child方法出现问题,@transaction 是基于aop的方式进行事务控制的(cglibaopproxy.java进行方法拦截,transactioninterceptor.java进行代理对象调用执行方法),需要使用的是代理对象调用方法,上面的代码使用的还是this,即当前实例化对象,因此执行this.child(),方法不能被拦截增强;
将上面的parent方法修改如下
@autowired private applicationcontext context; private orderservice orderservice; @postconstruct public void init() { orderservice = context.getbean(orderservice.class); } @transactional @override public void parent() { try { //获取代理对象,通过代理对象调用child() orderservice.child(); //获取代理对象 //orderservice orderservice = (orderservice) aopcontext.currentproxy(); //orderservice.child(); } catch (exception e) { log.error("插入异常", e); } order order = new order(); order.setorderno("parent"); order.setstatus("0"); order.settitle("parent"); order.setamount("1000"); ordermapper.insert(order); }
执行parent方法,当出现异常的时候,事务会进行回滚;
如果想实现当调用parent方法时,调用child方法发生异常,只回滚child方法插入的数据,parent方法插入的数据不回滚,修改如下
@transactional(propagation = propagation.requires_new) @override public void child() { order order = new order(); order.setorderno("child"); order.setstatus("0"); order.settitle("child"); order.setamount("2000"); ordermapper.insert(order); throw new runtimeexception(); }
@transactional(propagation = propagation.requires_new)
如果当前存在事务,则挂起事务并开启一个新事务执行,新事务执行完毕后,唤醒之前的挂起的事务,则继续执行;如果当前不存在事务,则新建一个事务;
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。