转载

Spring事务传播属性@Transactional和AOP的坑

Spring声明式事务操作简单,我们平常开发过程中,只需要在需要事务控制的方法上面加上@Transactional注解就可以绑定事务控制。但是其中的参数配置今天给大家捋一捋,并且有个AOP的神坑需要大家注意。

传播属性 特点
REQUIRED 默认的传播属性,表示如果当前环境存在事务就保持此事务执行,否则新开一个事务并且在新事务中执行
REQUIRES_NEW 表示不管当前环境是否存在事务,都新建一个事务并在新事务中执行,将原有事务进行挂起
SUPPORTS 表示如果当前环境存在事务,就在此事务中执行,否则不以事务方式执行
NOT_SUPPORTED 表示此方法不进行事务控制,如果当前环境存在事务,则挂起
MANDATORY 表示此方法必须在一个事务中进行,如果当前环境没有事务则抛出异常
NEVER 表示此方法运行不能有事务控制,一旦有事务传播至此就抛出异常
NESTED 表示如果事务存在,则运行在一个嵌套的事务中,如果没有事务,则按REQUIRED属性执行

常用的属性一般是REQUIRED和REQUIRES_NEW这两个。 下面我们用代码来验证一下这常见的两个属性:

1.开启事务支持 @EnableTransactionManagement

/**
 * @author wangzhi
 */
@SpringBootApplication
@EnableTransactionManagement
public class DemoApplication {
    public static void main(String[] args) {
        new SpringApplication(DemoApplication.class).run(args);
    }
}
复制代码

2.实体类和数据库

/**
 * @author wangzhi
 */
@Data
@TableName("course")
public class CourseEntity {
    
    @TableId(type = IdType.AUTO)
    private Integer id;
    
    private String courseName;
    
    private BigDecimal price;
}
复制代码
Spring事务传播属性@Transactional和AOP的坑

3.service层业务代码

我们先检验一下 REQUIRED

/**
 * @author wangzhi
 */
@Service
public class TransactionService {

    @Autowired
    private CourseMapper courseMapper;

    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    public void save() {
        CourseEntity course = new CourseEntity();
        course.setCourseName("语文");
        course.setPrice(new BigDecimal(100));
        courseMapper.insert(course);
        //故意制造异常
        System.out.println(1/0);
    }

    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    public void saveInit() {
        CourseEntity course = new CourseEntity();
        course.setCourseName("数学");
        course.setPrice(new BigDecimal(100));
        courseMapper.insert(course);
        //调用save方法
        try{
            save();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
复制代码

4.测试一下,你们猜数据库有几条记录?

@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = DemoApplication.class)
class DemoApplicationTests {

    @Autowired
    TransactionService transactionService;

    @Test
    public void transactionTest() throws Exception {
        transactionService.saveInit();
    }

}
复制代码

5.看结果

Spring事务传播属性@Transactional和AOP的坑

4.别急,再看看REQUIRES_NEW

/**
 * @author wangzhi
 */
@Service
public class TransactionService {

    @Autowired
    private CourseMapper courseMapper;

    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
    public void save() {
        CourseEntity course = new CourseEntity();
        course.setCourseName("语文");
        course.setPrice(new BigDecimal(100));
        courseMapper.insert(course);
        //故意制造异常
        System.out.println(1/0);
    }

    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    public void saveInit() {
        CourseEntity course = new CourseEntity();
        course.setCourseName("数学");
        course.setPrice(new BigDecimal(100));
        courseMapper.insert(course);
        //调用save方法
        try {
            save();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
复制代码
Spring事务传播属性@Transactional和AOP的坑

5.之所以出现如此情况,并不是事务传播有问题,而是动态代理造成的。在同一个类里面互相调用事务方法,切面切的是发起事务的方法,而内部不管调用的什么事务方法都会默认为this当前对象去调动普通方法,这些事务注解说白了就是不管用。事务是根据动态代理生成的动态对象去执行事务的控制,所以在同类方法内部调用其他事务方法必须要获取其对应的代理对象去调用才生效,否则必须放在不同的类里面。结局方法:

6.引入AOP

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
复制代码

7.@EnableAspectJAutoProxy(exposeProxy = true)

/**
 * @author wangzhi
 */
@SpringBootApplication
@EnableTransactionManagement
@MapperScan("com.example.mapper")
@EnableAspectJAutoProxy(exposeProxy = true)
public class DemoApplication {
    public static void main(String[] args) {
        new SpringApplication(DemoApplication.class).run(args);
    }
}
复制代码

8.改造一下service

/**
 * @author wangzhi
 */
@Service
public class TransactionService {

    @Autowired
    private CourseMapper courseMapper;

    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
    public void save() {
        CourseEntity course = new CourseEntity();
        course.setCourseName("语文");
        course.setPrice(new BigDecimal(100));
        courseMapper.insert(course);
        //故意制造异常
        System.out.println(1/0);
    }

    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    public void saveInit() {
        CourseEntity course = new CourseEntity();
        course.setCourseName("数学");
        course.setPrice(new BigDecimal(100));
        courseMapper.insert(course);
        //调用save方法
        try {
            TransactionService proxy = (TransactionService)AopContext.currentProxy();
            proxy.save();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
复制代码

看看结果:

Spring事务传播属性@Transactional和AOP的坑

这样的结果就对了!所以我们在开发过程中遇到A方法调用B方法,如果AB方法都在同一个类里面,想要B方法的事务生效必须用代理方式执行。当然AB方法不在同一个类里面可以生效,因为动态代理是运行时才构造的代理对象。而且,类似的技术点不仅仅出现在事务这里,比如@Async注解同样还是这样,同一个类里面调用依然不是异步执行,当涉及到动态代理相关都要注意此点。

原文  https://juejin.im/post/5f12b820f265da22ed512ba1
正文到此结束
Loading...