【Spring基础系列4】注解@Transactional(二)

简介: 前面已经讲解了IOC的基础知识,以及Spring常用的注解,这篇文章是对上一篇文章《【Spring基础系列3】Spring常用的注解》的补充,由于这个注解需要讲述的内容比较多,一方面该注解非常重要,另一方面非常容易入坑,所以这个注解的内容,就单独放到这篇文章来讲。

事务不生效的几种case


所有的测试Case,测试完毕后,DB数据需要手动更新成原始数据,保证测试Case互不影响。


Case1: 类内部访问

简单来讲就是指非直接访问带注解标记的方法 B,而是通过类普通方法 A,然后由 A 访问 B,下面是一个简单的 case,我们在类UserController中新增一个方法testFail():

public void testFail() throws Exception {
    testSuccess();
    throw new Exception("测试事务回滚不生效");
}


这里我们是通过testFail()调用testSuccess(),再看一下测试用例:

public static void main(String[] args) throws Exception {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserController uc = (UserController) applicationContext.getBean("userController");
    try {
        uc.testFail();
    } finally {
        MyUser user =  uc.query(1);
        System.out.println("修改后的记录:" + user);
    }
}
// 输出:
// 原记录:User[uid=1,uname=张三,usex=女]
// 修改后的记录:User[uid=1,uname=张三,usex=女]

从上面的输出可以看到,事务并没有回滚,主要是因为类内部调用,不会通过代理方式访问。


Case2: 私有方法

在私有方法上,添加@Transactional注解也不会生效,私有方法外部不能访问,所以只能内部访问,上面的 case 不生效,这个当然也不生效了:

@Transactional(rollbackFor = Exception.class)
private void testSuccess() throws Exception {
    Integer id = 1;
    MyUser user = query(id);
    System.out.println("原记录:" + user);
    update(id);
    throw new Exception("测试事务生效");
}

直接使用时,下面这种场景不太容易出现,因为 IDEA 会有提醒,文案为: Methods annotated with '@Transactional' must be overridable


Case3: 异常不匹配

@Transactional注解默认处理运行时异常,即只有抛出运行时异常时,才会触发事务回滚,否则并不会回滚:

@Transactional
public void testSuccess() throws Exception {
    Integer id = 1;
    MyUser user = query(id);
    System.out.println("原记录:" + user);
    update(id);
    throw new Exception("测试事务生效");
}


测试 case 如下:

public static void main(String[] args) throws Exception {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserController uc = (UserController) applicationContext.getBean("userController");
    try {
        uc.testSuccess();
    } finally {
        MyUser user =  uc.query(1);
        System.out.println("修改后的记录:" + user);
    }
}
// 输出:
// 原记录:User[uid=1,uname=张三,usex=女]
// 修改后的记录:User[uid=1,uname=张三-test,usex=女]

输出结果如下,事务并未回滚(如果需要解决这个问题,通过设置@Transactional的 rollbackFor 属性即可)


Case4: 多线程

这个场景可能并不多见,在标记事务的方法内部,另起子线程执行 db 操作,此时事务同样不会生效

下面给出两个不同的姿势,一个是子线程抛异常,主线程 ok;一个是子线程 ok,主线程抛异常。


父线程抛出异常

我们在类UserController中新增一个方法testMultThread(),该方法在主线程会抛出异常:

public void testSuccess() throws Exception {
    Integer id = 1;
    MyUser user = query(id);
    System.out.println("原记录:" + user);
    update(id);
}
@Transactional(rollbackFor = Exception.class)
public void testMultThread() throws Exception {
    new Thread(new Runnable() {
        @SneakyThrows
        @Override
        public void run() {
            testSuccess();
        }
    }).start();
    throw new Exception("测试事务不生效");
}

上面这种场景不生效很好理解,子线程的异常不会被外部的线程捕获,testMultThread这个方法的调用不抛异常,因此不会触发事务回滚,调用方式和前面一样,就换一个方法:

uc.testMultThread();

这里提醒一下,输出的数据因为都是在主线程中输出,所以输出结果都是“张三”,后来我看了库表,发现DB数据已经更新为“张三-testing”,所以事务回滚没有生效。


子线程抛出异常

我们修改代码如下,让子任务抛出异常:

public void testSuccess() throws Exception {
    Integer id = 1;
    MyUser user = query(id);
    System.out.println("原记录:" + user);
    update(id);
    throw new Exception("测试事务生效");
}
@Transactional(rollbackFor = Exception.class)
public void testMultThread() throws Exception {
    new Thread(new Runnable() {
        @SneakyThrows
        @Override
        public void run() {
            testSuccess();
        }
    }).start();
}

同上,DB数据没有回滚,发现DB数据已经更新为“张三-testing”,所以事务回滚没有生效。


Case5: 传播属性

这个内容,我后面再单独出一篇文章来讲,要不然这篇文章篇幅又太长了。


后记


感觉@Transactional这个注解需要了解的内容稍微有点多,不过这个注解是我们绕不开的,所以需要大家好好学习,特别是事务不生效的坑!其中有一个坑“类内部访问”,我刚开始写JAVA是就踩过,后来还是同事告诉我的,现在回顾当时的状态,感觉真的是菜的不能再菜了。

文章后面遗留了“传播属性”的坑,这个我就先放一下,等后面有时间我再写,然后关于Spring的部分,还剩下AOP,这几天我会先把相关知识看完,然后再通过文章的形式分享出来,敬请期待...

相关文章
|
1天前
|
运维 Java 程序员
Spring5深入浅出篇:基于注解实现的AOP
# Spring5 AOP 深入理解:注解实现 本文介绍了基于注解的AOP编程步骤,包括原始对象、额外功能、切点和组装切面。步骤1-3旨在构建切面,与传统AOP相似。示例代码展示了如何使用`@Around`定义切面和执行逻辑。配置中,通过`@Aspect`和`@Around`注解定义切点,并在Spring配置中启用AOP自动代理。 进一步讨论了切点复用,避免重复代码以提高代码维护性。通过`@Pointcut`定义通用切点表达式,然后在多个通知中引用。此外,解释了AOP底层实现的两种动态代理方式:JDK动态代理和Cglib字节码增强,默认使用JDK,可通过配置切换到Cglib
|
1天前
|
Java 关系型数据库 MySQL
【JavaEE】Spring事务-@Transactional参数介绍-事务的隔离级别以及传播机制
【JavaEE】Spring事务-@Transactional参数介绍-事务的隔离级别以及传播机制
8 0
|
1天前
|
消息中间件 Java 关系型数据库
【JavaEE】Spring事务-事务的基本介绍-事务的实现-@Transactional基本介绍和使用
【JavaEE】Spring事务-事务的基本介绍-事务的实现-@Transactional基本介绍和使用
6 0
|
1天前
|
存储 缓存 Java
【JavaEE】Spring中注解的方式去获取Bean对象
【JavaEE】Spring中注解的方式去获取Bean对象
2 0
|
1天前
|
存储 Java 对象存储
【JavaEE】Spring中注解的方式去存储Bean对象
【JavaEE】Spring中注解的方式去存储Bean对象
5 0
|
1天前
|
JSON 前端开发 Java
【JAVA进阶篇教学】第七篇:Spring中常用注解
【JAVA进阶篇教学】第七篇:Spring中常用注解
|
1天前
|
JavaScript Java 开发者
Spring Boot中的@Lazy注解:概念及实战应用
【4月更文挑战第7天】在Spring Framework中,@Lazy注解是一个非常有用的特性,它允许开发者控制Spring容器的bean初始化时机。本文将详细介绍@Lazy注解的概念,并通过一个实际的例子展示如何在Spring Boot应用中使用它。
21 2
|
1天前
|
前端开发 Java
SpringBoot之自定义注解参数校验
SpringBoot之自定义注解参数校验
19 2
|
1天前
|
Java 数据库 Spring
【spring(四)】Spring事务管理和@Transactional注解
【spring(四)】Spring事务管理和@Transactional注解
|
12月前
|
Java 数据库 Spring
Spring中的@Transactional注解放在类上和方法上有什么不同?
Spring中的@Transactional注解放在类上和方法上有什么不同?
178 0