Spring事务失效详解

前置知识

事务:指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。可以理解事务就是一段代码块或者一行SQL,这段代码或这行SQL会更新数据库,事务具有基本的ACID特性,以此保障数据的安全性。

事务管理:由事务管理器1、恢复管理器2、锁管理器3、死锁管理器4、缓存管理器5构成

事务管理的作用:管理事务相关的资源;更容易处理复杂的事务;简化事务相关的操作,让程序员更关注业务

Spring中提供了两种事务管理机制

编程式事务:是指在代码中手动的管理事务的提交、回滚等操作,代码侵入性比较强。(Spring提供了TransactionTemplate模板,利用该模板我们可以通过编程的方式实现事务管理,而无需关注资源获取、复用、释放、事务同步及异常处理等操作。相对于声明式事务来说,这种方式相对麻烦一些,但是好在更为灵活,我们可以将事务管理的范围控制的更为精确)

声明式事务:基于AOP面向切面的,它将具体业务与事务处理部分解耦,代码侵入性很低。而声明式事务有两种方式实现,方式一是基于@Transaction注解实现,方式二是基于XML实现。(Spring事务管理的亮点在于声明式事务管理,它允许我们通过声明的方式,在IoC配置中指定事务的边界和事务属性,Spring会自动在指定的事务边界上应用事务属性。相对于编程式事务来说,这种方式十分的方便,只需要在需要做事务管理的方法上,增加@Transactional注解,以声明事务特征即可)

大多数 Spring 框架的用户选择声明式事务管理,因为它对应用代码的影响最小, 因此更符合一个无侵入的轻量级容器的思想。声明式事务管理要优于编程式事务 管理,虽然比编程式事务管理(这种方式允许你通过代码控制事务)少了一点灵 活性。

@Transactional:可以作用在接口、类、方法

作用于接口:不推荐这种使用方法,因为一旦标注在Interface上并且配置了Spring AOP 使用CGLib动态代理,将会导致@Transactional注解失效

作用于类:当把@Transactional 注解放在类上时,代表这个类所有公共(public)非静态(static)的方法都将启用事务功能,且都会被 Spring 的事务管理器进行管理

作用于方法:当把@Transactional配置在方法上,该方法被当成一个独立的事务,且被事务管理器管理。当类配置了@Transactional,方法也配置了@Transactional,此时方法的事务会覆盖类的事务配置信息

@Transactional的属性

propagation属性

propagation 代表事务的传播行为,默认值为 Propagation.REQUIRED,其他的属性信息如下:

Propagation.REQUIRED:如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。 也就是说如果A方法和B方法都添加了注解,在默认传播模式下,A方法内部调用B方法,会把两个方法的事务合并为一个事务

Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行

Propagation.MANDATORY:如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常

Propagation.REQUIRES_NEW:重新创建一个新的事务,如果当前存在事务,暂停当前的事务。( 当类A中的 a 方法用默认Propagation.REQUIRED模式,类B中的 b方法加上采用 Propagation.REQUIRES_NEW模式,然后在 a 方法中调用 b方法操作数据库,然而 a方法抛出异常后,b方法并没有进行回滚,因为Propagation.REQUIRES_NEW会暂停 a方法的事务 )

Propagation.NOT_SUPPORTED:以非事务的方式运行,如果当前存在事务,暂停当前的事务

Propagation.NEVER:以非事务的方式运行,如果当前存在事务,则抛出异常

Propagation.NESTED :和 Propagation.REQUIRED 效果一样

isolation属性

事务的隔离级别,默认值为 Isolation.DEFAULT

Isolation.DEFAULT:使用底层数据库默认的隔离级别。

Isolation.READ_UNCOMMITTED:读取未提交数据(会出现脏读, 不可重复读) 基本不使用

Isolation.READ_COMMITTED:读取已提交数据(会出现不可重复读和幻读)

Isolation.REPEATABLE_READ:可重复读(会出现幻读)

Isolation.SERIALIZABLE:串行化

timeout属性:事务的超时时间,默认值为 -1。如果超过该时间限制但事务还没有完成,则自动回滚事务

readOnly属性:指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 readonly 为 true

rollbackFor属性:用于指定能够触发事务回滚的异常类型,可以指定多个异常类型

noRollbackFor属性:抛出指定的异常类型,不回滚事务,也可以指定多个异常类型

事务不生效

场景:@Transactional应用在非 public 修饰的方法上

@Transactional注解修饰的方法必须是public修饰的,同样的@Transactional修饰类时,也只有类中使用pulbic修饰的方法才能成为事务。

须知:使用@Transactional修饰的方法,必须是public修饰、非static修饰、非final修饰的,一个不满足就会导致事务失效

在AbstractFallbackTransactionAttributeSource类的computeTransactionAttribute方法中有个判断,如果目标方法不是 public,则TransactionAttribute返回 null,即不支持事务。

场景:方法调用导致@Transactional失效

同一个类中,A方法是非事务性方法,但是B方法是事务性方法,此时A调用B就会导致B的事务失效。

这个和场景一的原因是类似的,事务的实现是基于AOP的,而AOP的实现又是基于动态代理的,而动态代理的本质就算对方法的增强,如果想要使用增强的方法(也就是想要使用事务方法),就必须是通过代理对象去触发目标对象的方法。

注意:要在启动类添加@EnableAspectJAutoProxy(exposeProxy = true)开启注解版的AOP功能

解决方案:

通过AopContext.currentProxy()这个API获取当前类的代理对象,也可以注入当前类而且不会出现循环依赖。

@Service
Class ServiceImpl implements IService{
    // 普通方法
	@Override
    public Result A() {
        ......
        IService proxy = (Iservice) AopContext.currentProxy();
        return proxy.B();
    }
	// 事务方法
    @Override
    @Transactional
    public Result B() {
        ......
        return Result.ok();
    }
}

场景:数据库引擎不支持事务

Spring的事务本质还是得靠数据库引擎的支持,如果数据库引擎不支持事务,那么Spring就算使用事务也是白搭。常用的MySQL数据库默认使用支持事务的innodb引擎。一旦数据库引擎切换成不支持事务的myisam,那事务就从根本上失效了。当然相信这个问题出现的概率很小,但并不代表没有,还是需要有一定了解的

注意:从 MySQL 5.5.5 开始的默认存储引擎是:InnoDB,之前默认的都是:MyISAM。也就是说是从MySQL5.5.5开始,MySQL才支持事务

场景:Bean未被Spring管理

平时开发过程中,有个细节很容易被忽略,即使用 spring 事务的前提是:对象要被 spring 管理,需要创建 bean 实例。

Spring的事务管理核心是动态代理

通常情况下,我们通过 @Controller、@Service、@Component、@Repository 等注解,可以自动实现 bean 实例化和依赖注入的功能。

在SpringBoot中,只需要在启动类上添加@EnableAspectJAutoProxy(exposeProxy = true)这个注解,aopautoconfigure不一定生效

场景:多线程调用

spring 的事务是通过数据库连接来实现的。当前线程中保存了一个 map,key 是数据源,value 是数据库连接。

spring 的事务是通过LocalThread来保证线程安全的,事务和当前线程绑定,此时开启新线程执行业务,这个新线程的业务就会事务失效,因为事务是基于动态代理的,要想有事务,需要被动态代理。

private static final ThreadLocal<Map<Object, Object>> resources =
 
  new NamedThreadLocal<>("Transactional resources");

我们说的同一个事务,其实是指同一个数据库连接,只有拥有同一个数据库连接才能同时提交和回滚。如果在不同的线程,拿到的数据库连接肯定是不一样的,所以是不同的事务。

事务不回滚

场景: propagation 属性设置错误

当我们将propagation属性的值设置为一下几种取值就会导致事务失效:

Propagation.NOT_SUPPORTED:以非事务的方式运行,如果当前存在事务,暂停当前的事务

Propagation.NEVER:以非事务的方式运行,如果当前存在事务,则抛出异常

只有这三种传播特性才会创建新事务:REQUIRED,REQUIRES_NEW,NESTED

场景:rollbackFor属性设置错误(异常被捕获)

Spring默认抛出了未检查unchecked异常(继承自 RuntimeException 的异常)或者 Error才回滚事务;其他异常不会触发回滚事务。如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定 rollbackFor属性;若在目标方法中抛出的异常是 rollbackFor 指定的异常的子类,事务同样会回滚。

spring 事务,默认情况下只会回滚RuntimeException(运行时异常)和Error(错误),对于普通的 Exception(非运行时异常),它不会回滚。

@Slf4j
@Service
public class UserService {

    @Transactional
    public void add(UserModel userModel) throws Exception {
        try {
            saveData(userModel);
            updateData(userModel);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new Exception(e);
        }
    }
}

如上面这种情况,捕获了异常又抛出了Exception异常,事务也不会回滚

  • 20
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值