基本概念
事务传播行为就是用来描述一个方法被调用时事务应该执行的动作
class A {
public void method1() {
}
}
class B {
A a;
public void method2() {
a.method1();
}
}
以上述代码为例,B对象中调用了a对象的method1()方法,如果在method1()上开启了事务,此时需要说明method1()需要在method2()中执行怎样的动作,也就是说method2()需要怎样影响到method1()
PS:如果是在同一个方法里进行调用,即B类的method2()调用B类的method1()相当于普通方法调用,也就是AOP不生效的场景之一!
Spring传播行为
spring-tx.jar的org.springframework.transaction package下有一个事务定义信息接口
public interface TransactionDefinition {
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;
int PROPAGATION_NESTED = 6;
int ISOLATION_DEFAULT = -1;
int ISOLATION_READ_UNCOMMITTED = 1;
int ISOLATION_READ_COMMITTED = 2;
int ISOLATION_REPEATABLE_READ = 4;
int ISOLATION_SERIALIZABLE = 8;
int TIMEOUT_DEFAULT = -1;
...
}
PROPAGATION前缀的常量即为事务传播行为定义
| 常量 | 含义 |
|---|---|
| PROPAGATION_REQUIRED | 当前存在事务就加入事务,当前不存在事务就新建一个事务 |
| PROPAGATION_SUPPORTS | 当前存在事务就加入事务,当前不存在事务就以非事务方式执行 |
| PROPAGATION_MANDATORY | 当前存在事务就加入事务,当前不存在事务就抛出异常 |
| PROPAGATION_REQUIRES_NEW | 不管当前是否存在事务,都开启一个新的事务 |
| PROPAGATION_NOT_SUPPORTED | 以非事务方式执行,当前存在事务就把事务挂起 |
| PROPAGATION_NEVER | 以非事务方式执行,当前存在事务就抛出异常 |
| PROPAGATION_NESTED | 当前存在事务就以嵌套事务方式执行,当前不存在事务就新建一个事务 |
典型案例
1.对事务进行异常捕获操作
Service1
@Transactional(propagation = Propagation.REQUIRED)
public void addRequired() {
jdbcTemplate.update("insert into t_user(user_name,user_age,create_time) values(?, ?, ?)",
"张三", 20, new Date());
}
Service2
@Transactional(propagation = Propagation.REQUIRED)
public void addRequired() {
jdbcTemplate.update("insert into t_user(user_name,user_age,create_time) values(?, ?, ?)",
"李四", 20, new Date());
throw new RuntimeException();
}
Service3
@Transactional(propagation = Propagation.REQUIRED)
public void addRequired() {
try {
service2.addRequired();
} catch (Exception e) {
System.out.println(e);
}
service1.addRequired();
}
Service3中分别调用了service1和service2的事务方法,12方法都因为设置了REQUIRED传播属性,所以会加入到Service3的事务当中,service3中对service2的操作进行了异常捕获操作,内层service2的事务发生了异常进行事务回滚后,spring会把当前事务标记为“rollback-only”,虽然进行了try-catch捕获可以继续向下执行到service1的方法调用,执行完毕后外部事务也处理完毕。当要提交时,spring发现事务已经被标记为“rollback-only”了,但是方法却正常执行完毕,这时spring就会抛出UnexpectedRollbackException
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
2.嵌套事务的异常捕获
Service1
@Transactional(propagation = Propagation.NESTED)
public void addRequired() {
jdbcTemplate.update("insert into t_user(user_name,user_age,create_time) values(?, ?, ?)",
"张三", 20, new Date());
}
Service2
@Transactional(propagation = Propagation.NESTED)
public void addRequired() {
jdbcTemplate.update("insert into t_user(user_name,user_age,create_time) values(?, ?, ?)",
"李四", 20, new Date());
throw new RuntimeException();
}
Service3
@Transactional(propagation = Propagation.REQUIRED)
public void addRequired() {
try {
service2.addRequired();
} catch (Exception e) {
System.out.println(e);
}
service1.addRequired();
}
这里Serivce1和Service2事务方法都被NESTED嵌套事务传播行为修饰,所以二者都会在Service3事务里开启嵌套子事务执行,虽然service2方法抛出了异常,但因为外部事务进行了捕获操作,嵌套的子事务抛出的异常没有逐级向上抛出,而是由外部事务捕获打印,service1的方法得以成功执行。而如果不对其try-catch捕获,或者在catch里接着抛出,那么就相当于该外部事务会因为异常执行回滚操作,导致都不会执行成功。
3.新建事务的异常处理
Service1
@Transactional(propagation = Propagation.NESTED)
public void addRequired() {
jdbcTemplate.update("insert into t_user(user_name,user_age,create_time) values(?, ?, ?)",
"张三", 20, new Date());
}
Service2
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void addRequired() {
jdbcTemplate.update("insert into t_user(user_name,user_age,create_time) values(?, ?, ?)",
"李四", 20, new Date());
throw new RuntimeException();
}
Service3
@Transactional(propagation = Propagation.REQUIRED)
public void addRequired() {
try {
service2.addRequired();
} catch (Exception e) {
System.out.println(e);
}
service1.addRequired();
}
这里的Service2由NESTED改成了REQUIRES_NEW,和案例2其实是一个原理,区别在于Service2这里是新开启了一个事务,即便是抛出了异常同样被外部事务所捕获,导致Service1可以正常执行,一旦异常抛到外部事务里,同样都不会执行。
PS:思考一下,如果把Service1的NESTED也替换成REQUIRES_NEW,那Service1方法会执行成功吗?
答案是:只要对service2进行异常捕获,就可以使service1的方法得以成功执行,即便是不对service2进行异常捕获,只要把service1方法执行放到service2之前新开启一个事务或者嵌套事务都可以执行。只要是try-catch捕获的方法开启的事务和外部事务不是同一个,那前边所说的那个因为“rollback-only”状态而产生的异常也就不会被抛出。被try-catch捕获的方法也就相当于是正常执行状态,只是其事务被回滚而已,不会影响到其他新开启事务的正常执行