​ 在SpringBoot框架中,当我们需要用到事务时,一般直接在方法上标注@Transactional,这样当方法内发生异常时,整个方法都会回滚。但Spring的事务存在一个问题,假设存在这么一个场景:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Service{

@Transactional(timeout = 30)
public void a(){
b();
c();
}

@Transactional(propagation = Propagation.REQUIRED, timeout = 10)
public void b(){

}

@Transactional(propagation = Propagation.REQUIRES_NEW, timeout = 10)
public void c(){

}
}

​ 在spring中,事务的默认传播模式是propagation = Propagation.REQUIRED,即当b()方法需要一个事务,a()方法的事务会默认传递给b()方法,那么当a()方法中任何一处抛出异常,都会连同b()方法一起回滚。但存在的问题是。此时在b()方法中不论对事务做了什么设置,都是无效的,因为用的是a()方法的事务。再看c()方法,声明了propagation = Propagation.REQUIRES_NEW,即使用一个新事务,这样虽然配置能生效,但当a()方法抛出异常时,c()方法不会跟着一起回滚,显然达不到业务需求。

​ 造成上面的原因是,同一个对象内的事务方法互相调用,绕过了代理对象。因为spring中的事务是通过代理对象实现的,那么能不能这样实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Service{
@Autowired
private Service service;

@Transactional(timeout = 30)
public void a(){
service.b();
service.c();
}

@Transactional(propagation = Propagation.REQUIRED, timeout = 10)
public void b(){

}

@Transactional(propagation = Propagation.REQUIRED, timeout = 10)
public void c(){

}
}

这样通过代理对象调用方法,虽然配置会生效,但这会出现循环依赖问题,显然也不行。

正确的解决方法是使用代理对象来解决:

  1. 引入aop

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
  2. 在启动类标注@EnableAspectJAutoProxy(exposeProxy = true)

    spring aop中有两种代理模式,一种是jdk动态代理,另外一种是cglib代理。

    ​ jdk动态代理是JDK原生的,不需要任何依赖即可使用,缺点是如果要使用JDK动态代理,被代理的类必须实现了接口,否则无法代理;JDK动态代理无法为没有在接口中定义的方法实现代理,假设我们有一个实现了接口的类,我们为它的一个不属于接口中的方法配置了切面,Spring仍然会使用JDK的动态代理,但是由于配置了切面的方法不属于接口,为这个方法配置的切面将不会被织入。JDK动态代理执行代理方法时,需要通过反射机制进行回调,此时方法执行的效率比较低。

    ​ cglib代理的优点是,使用CGLib代理的类,不需要实现接口,因为CGLib生成的代理类是直接继承自需要被代理的类;CGLib生成的代理类是原来那个类的子类,这就意味着这个代理类可以为原来那个类中,所有能够被子类重写的方法进行代理;CGLib生成的代理类,和我们自己编写并编译的类没有太大区别,对方法的调用和直接调用普通类的方式一致,所以CGLib执行代理方法的效率要高于JDK的动态代理。

    ​ 在这里我们使用cglib代理,并配置对外暴露代理对象

做好这些配置后,回到我们最初的场景,这时候我们可以这样做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Service{	
@Transactional(timeout = 30)
public void a(){
//拿到当前类的代理对象
Service service = (Service)AopContext.currentProxy();
service.b();
service.c();
}

@Transactional(propagation = Propagation.REQUIRED, timeout = 10)
public void b(){

}

@Transactional(propagation = Propagation.REQUIRED, timeout = 10)
public void c(){

}
}