Spring原理系列:事务番外篇- 事务的失效

  • 前言
  • 一.事务失效的几种常见情形
  • 1.1 public修饰符
  • 1.2 MVC/Spring相关
  • 1.3 异常类型
  • 1.4 动态代理相关
  • 1.5 数据库相关
  • 1.6 事务传播特性


前言

首先希望读者阅读下我这篇文章:Spring源码系列:事务原理。在看了源码的基础上,相信会对下面事务失效的场景和原因有更好的理解。

一.事务失效的几种常见情形

1.1 public修饰符

首先,public的方法事务不生效,事务只在public修饰的方法上起作用,原因见源码:

if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
	return null;
}

1.2 MVC/Spring相关

第一种:若采用MVC+Spring的配置方式,即

  • MVC配置文件配置了context:component-scan扫描。
  • Spring配置文件配置了事务。

此时事务可能会失效。因为Spring是先加载MVC配置文件,再加载Spring配置文件。若此时通过扫描,将相关的业务bean加载到容器当中了。而此时事务并没有被加载进来(因为是在Spring配置文件中配置的),而Spring事务从本文的源码上来看,是基于AOP来实现的,故导致事务无法被注入到对应的业务bean中进行增强。


第二种:使用Spring事务的前提是,对象需要别Spring管理 ,而bean没有被加载到容器当中的情况就有很多:

  • XML配置文件没有配置这个类。
  • 该类没有加注解:@Service@Component等。
  • MVC扫描时,没有扫描到这个类。

1.3 异常类型

第一种:默认情况下,Spring事务只会对RuntimeExceptionError类型的错进行回滚,而Exception类型则不会回滚。(若有需要,需自行配置)对应源码如下:

public class DefaultTransactionAttribute extends DefaultTransactionDefinition implements TransactionAttribute {
	@Override
	public boolean rollbackOn(Throwable ex) {
		return (ex instanceof RuntimeException || ex instanceof Error);
	}
}

第二种:用户自己吞掉了异常。
例如:

@Transactional
public void add(User user) {
    try {
        save(user);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

原因和第一种类似,开发者自己捕捉了异常,但是并没有将其抛出,只有抛出去了,才能让Spring事务感知到呀!

1.4 动态代理相关

第一种:方法用了final修饰。

例如伪代码:

@Override
public final void insert(User user) throws Exception {
    save(user);
}

原因:

  • Spring事务的底层是在AOP的基础上实现的。
  • 也就是说通过JDK动态代理或者是Cglib动态代理,生成了对应的代理类,实现事务的增强功能。
  • 那倘若对应的方法用final修饰,那么在代理的过程中,无法重写,进而无法增强事务功能。

第二种:方法的内部调用。

例如伪代码:我们在方法update中调用事务代码insert

public class UserServiceImpl implements UserService {
    private JdbcTemplate jdbcTemplate;
    
    public void update(User user) throws Exception {
        insert(user);
    }

    @Transactional
    @Override
    public void insert(User user) throws Exception {
		save(user);
	}
}

还是一样的道理,事务是通过AOP来实现的,但是上述伪代码,在update方法中,调用了事务方法,相当于调用了this对象的方法,因此insert方法的事务并不会生效。

1.5 数据库相关

我们知道,在mysql5之前,数据库默认的引擎是mysiam,而不是InnoDB。我们仅仅从事务的角度来出发:

  • mysiam:不支持事务。
  • InnoDB:支持事务。

1.6 事务传播特性

我们知道,Spring中事务的类型有这么几种:

  • PROPAGATION_REQUIRED:支持当前事务,如果当前没有事务,就新建一个事务。
  • PROPAGATION_SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。
  • PROPAGATION_MANDATORY:支持当前事务,如果当前没有事务,就抛出异常。
  • PROPAGATION_REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。
  • PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  • PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
  • PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,就新建一个事务。

因此当你配置了事务,但是你的代码却这么写:

@Transactional(propagation = Propagation.NEVER)
public void add(User user) {
    try {
        save(user);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

此时,若存在事务,则会抛异常,事务必然失效。

同理,目前有REQUIREDREQUIRES_NEWNESTED肯定是支持事务的。(没有事务,则创建)