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事务只会对RuntimeException
和Error
类型的错进行回滚,而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();
}
}
此时,若存在事务,则会抛异常,事务必然失效。
同理,目前有REQUIRED
、REQUIRES_NEW
、NESTED
肯定是支持事务的。(没有事务,则创建)