面试官:Spring@Transactional注解在什么情况下事务不生效?

简介: 这篇笔记来学习一下使用Spring框架的时候,@Transactional注解标注的方法在什么情况下事务不会生效。我们可以写一个demo项目,引入以下依赖

这篇笔记来学习一下使用Spring框架的时候,@Transactional注解标注的方法在什么情况下事务不会生效。

我们可以写一个demo项目,

引入以下依赖

<dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.2.8.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.8.RELEASE</version>
        </dependency>
    </dependencies>

项目的目录结构如下:

我们新建一个user表,之后会用上

CREATE TABLE `tb_user` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL DEFAULT '',
  `age` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=50 DEFAULT CHARSET=utf8

然后我们需要在 site.nemo.entity 包里面定义一个User类:

package site.nemo.entity;
public class User {
    private Integer id;
    private String name;
    private Integer age;
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

然后我们还需要一个Dao,来操作user表。在 site.nemo.dao 包下新建一个UserDao类:

@Repository
public class UserDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    public void save(User user) {
        jdbcTemplate.update("insert into tb_user (name, age) values (?, ?)", user.getName(), user.getAge());
    }
}

然后我们还需要对jdbc的数据源进行一些配置,在 site.nemo.configuration 包里面新建一个配置类:

@Configuration
@ComponentScan(basePackages = {"site.nemo.service", "site.nemo.dao"})
public class TransactionConfiguration {
    @Bean
    public JdbcTemplate jdbcTemplate() {
        return new JdbcTemplate(dataSource());
    }
    @Bean
    public DataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://xxx.xxx.xxx.xxx:3306/mytestdb?characterEncoding=utf8");
        dataSource.setUsername("xxxx");
        dataSource.setPassword("xxxx");
        return dataSource;
    }
}

最后我们在 site.nemo.service 里面新建一个UserService:

@Service
public class UserService {
    @Autowired
    private UserDao userDao;
}

我们下面开始分析几个@Transactional没有起作用的原因

原因一:没有开启事务管理

我们给 UserService 添加一个方法,如下所示。

@Transactional
    public void save1() {
        User user = new User();
        user.setName("1");
        user.setAge(1);
        userDao.save(user);
        int i = 1 / 0;
    }

该方法是往tb_user表里面插入一条数据,在方法的最后有一个 1/0 ,肯定会报错。我希望的是,在该方法上标注了 @Transactional 注解,当它里面有异常的时候,能够事务回滚。也就是希望数据没有被插入数据库。

我们现在在 site.nemo 包下新建一个类 Application1 ,来调用 UserServicesave1 方法

public class Application1 {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(TransactionConfiguration.class);
        UserService userService = context.getBean(UserService.class);
        userService.save1();
    }
}

运行 Application1 的main方法,可以看到控制台打印出了错误堆栈。

但是, 1 这个用户还是被插入到数据库了。并没有事务回滚。

原因是,我们并没有添加 @
EnableTransactionManagement
来开启事务管理,所以 @Transactional 没生效。

当我们在 TransactionConfiguration 这个类上面加上 @
EnableTransactionManagement
注解之后,再执行 Application1 的main方法,可以看到数据没有插入即事务被回滚了。

我们知道如果使用的是springboot框架,它自动帮你开启了事务管理。可以从框架源码里面看一下

springboot框架的启动类都会加上 @SpringBootApplication 注解,而 @SpringBootApplication注解其实是 @EnableAutoConfiguration 注解和其他注解的组合。

@EnableAutoConfiguration 注解使用了@Import来引入
AutoConfigurationImportSelector.class
选择器。(关于如何使用@Import和ImportSelector来实现模块注解,可以看我的另外一篇笔记)

查看
AutoConfigurationImportSelector.class
的源码,在 selectImports 方法中找到了 getAutoConfigurationEntry 方法的调用。

进入 getAutoConfigurationEntry 方法看一下,它调用了
getCandidateConfigurations
方法

进入
getCandidateConfigurations
方法,可以看到它会去读spring.factories文件

可以知道
getCandidateConfigurations
方法会去读 spring.factories 文件,我们可以从项目的 Externnal Libraries 里面找到
org.springframework.boot:spring-boot-autoconfigure
,找到它的 META-INF 文件夹,可以看到里面有 spring.factories 文件

点开 spring.factories 文件,搜索一下与transaction有关的。

我们点进
TransactionAutoConfiguration
类里面去看一下,从它的注解看出来,它就是自动装配事务的

看一下
TransactionAutoConfiguration
类里面的具体内容,看到了我们熟悉的 @
EnableTransactionManagement
注解。

除了上面说的在普通的非springboot项目里面没有开启了事务管理 @
EnableTransactionManagement
这个原因导致@Transactional没有生效,下面也会分析以下其他原因。下面分析的几个case,都是在已经开启了事务管理 @
EnableTransactionManagement
的基础上。

原因二:标注了@Transactional的方法里面的异常被捕获了

我们给 UserService 再添加一个 save2 方法,如下所示。

@Transactional
    public void save2() {
      try {
            User user = new User();
          user.setName("2");
          user.setAge(2);
          userDao.save(user);
          int i = 1 / 0;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

我们现在在 site.nemo 包下新建一个类 Application2 ,来调用 UserServicesave2 方法

public class Application2 {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(TransactionConfiguration.class);
        UserService userService = context.getBean(UserService.class);
        userService.save2();
    }
}

从结果可知,事务没有回滚。

因为save2方法使用了try/catch捕获了异常,所以即使标注了@Transactional,这个方法也还是没回滚。

我们可以点进去@Transactional看下它的具体内容,可以看到它有一个 rollbackFor 属性。看下它的注释

注释里面提到,默认情况下,当发生 Error 或者 RuntimeException 的时候,才会回滚。

而我们的save2方法上面标注的@Transactional并没有指定 rollbackFor 属性,而且save2里面的异常被我们捕获了且没有再抛出来,所以save2没有回滚。

原因三:标注了@Transactional的方法发生了非 Error 或者 RuntimeException

从第二个原因里面可以知道,默认情况下,当发生 Error 或者 RuntimeException 的时候,才会回滚。

所以我们来试一下当抛出的不是这两个异常的时候,会不会回滚

我们可以自定义一个 BusinessException ,它继承的是Exception

package site.nemo.exceptions;
public class BusinessException extends Exception {
    public BusinessException() {
        super();
    }
    public BusinessException(String msg) {
        super(msg);
    }
}

然后在 UserService 里面再添加一个方法 save3

@Transactional
    public void save3() throws BusinessException {
        User user = new User();
        user.setName("3");
        user.setAge(3);
        userDao.save(user);
        throw new BusinessException("test save3");
    }

我们现在在 site.nemo 包下新建一个类 Application3 ,来调用 UserServicesave3 方法

public class Application3 {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(TransactionConfiguration.class);
        UserService userService = context.getBean(UserService.class);
        try {
            userService.save3();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

从结果可知,事务没有回滚。

关于java里面的unchecked和checked异常的区别,可以看我的另外一篇笔记

原因四:标注了@Transactional的方法的事务传播类型propagation配置成了NOTSUPPORT

事务的传播类型见我的另一篇笔记

NOTSUPPORT表示不支持事务,即使当前有事务,也不会使用事务。

所以当 propagation = Propagation.NOT_SUPPORTED 的时候,不会使用事务。所以异常发生的时候,也就不会回滚。

我们可以试验一下, 在 UserService 里面添加 save4 方法,在它上面声明@Transactional注解,并且设置 propagation = Propagation.NOT_SUPPORTED

@Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void save4() {
        User user = new User();
        user.setName("4");
        user.setAge(4);
        userDao.save(user);
        int i = 1 / 0;
    }

我们现在在 site.nemo 包下新建一个类 Application4 ,来调用 UserServicesave4 方法

public class Application4 {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(TransactionConfiguration.class);
        UserService userService = context.getBean(UserService.class);
        userService.save4();
    }
}

从结果可知,没有回滚。

原因五:标注了@Transactional的方法的事务传播类型propagation配置成了NEVER

NEVER表示不支持事务,如果有事务则会报错

我们可以试验一下, 在 UserService 里面添加 save5 方法,在它上面声明@Transactional注解,并且设置 propagation = Propagation.NEVER

@Transactional(propagation = Propagation.NEVER)
    public void save5() {
        User user = new User();
        user.setName("5");
        user.setAge(5);
        userDao.save(user);
        int i = 1 / 0;
    }

我们现在在 site.nemo 包下新建一个类 Application5 ,来调用 UserServicesave5 方法

public class Application5 {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(TransactionConfiguration.class);
        UserService userService = context.getBean(UserService.class);
        userService.save5();
    }
}

从结果可知,没有回滚。

原因六:标注了@Transactional的方法的事务传播类型propagation配置成了SUPPORTS且当前没有事务

SUPPORTS的意思是,如果当前有事务,就加入,如果没事务,则以非事务运行。

我们可以试验一下, 在 UserService 里面添加 save6 方法,在它上面声明@Transactional注解,并且设置 propagation = Propagation.SUPPORTS

@Transactional(propagation = Propagation.SUPPORTS)
    public void save6() {
        User user = new User();
        user.setName("6");
        user.setAge(6);
        userDao.save(user);
        int i = 1 / 0;
    }

我们现在在 site.nemo 包下新建一个类 Application6 ,来调用 UserServicesave6 方法

public class Application6 {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(TransactionConfiguration.class);
        UserService userService = context.getBean(UserService.class);
        userService.save6();
    }
}

从结果可知,没有回滚。

原因七:外部调用方法A,A内部调用方法B,A没有@Transaction注解而B有@Transactional注解

UserService 里面添加 save7 方法,和 save72 方法,其中 save72 上面标有@Transactional注解,且save72里面有异常

public void save7() {
        User user = new User();
        user.setName("7");
        user.setAge(7);
        userDao.save(user);
        save72();
    }
    @Transactional
    public void save72() {
        User user = new User();
        user.setName("72");
        user.setAge(72);
        userDao.save(user);
        int i = 1 / 0;
    }

我们现在在 site.nemo 包下新建一个类 Application7 ,来调用 UserServicesave7 方法

public class Application7 {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(TransactionConfiguration.class);
        UserService userService = context.getBean(UserService.class);
        userService.save7();
    }
}

从结果可以看出,save72方法没有回滚。

这是因为, save7 方法没有标注@Transactional注解,它内部调用 save72() 其实可以看做是 this.save72() ,这里的、this其实是个普通对象,没有被AOP动态代理增强过。所以 save72()出现异常的时候没有回滚。

那么其实我们也可以知道,如果save7和sav72上面都有@Transactional注解的话,事务最终会回滚,并不是因为save72上面的注解生效了,而是因为save7上面的注解生效了,save72回滚只不过是因为被包在了save7的事务里面,是在整个大事务里面回滚的。

原因八:标注了@Transactional的方法A的propagation配置成了REQUIRE,标注了@Transactional的方法B的propagation配置成了REQUIRE_NEW,方法A调用了方法B

REQUIRE表示如果当前有事务,则加入事务;如果当前没事务,则新起一个事务;

REQUEIRE_NEW表示不管当前是否有事务,都新起一个事务

标注了@Transactional的方法A的propagation配置成了REQUIRE,标注了@Transactional的方法B的propagation配置成了REQUIRE_NEW,方法A调用了方法B。那么当A最后因为异常回滚的时候,B不会回滚。

实验一下,可以新建一个 UserService2 ,里面有一个 save8() ,它的事务传播类型是 REQUIREDUserService2 有一个属性是 UserService 的实例。在 UserService2#save8() 里面会调用 UserService#save82() 方法。

@Service
public class UserService2 {
    @Autowired
    private UserDao userDao;
    @Autowired
    private UserService userService;
    @Transactional(propagation = Propagation.REQUIRED)
    public void save8() {
        User user = new User();
        user.setName("service2-8");
        user.setAge(8);
        userDao.save(user);
        userService.save82();
        int i = 1/0;
    }
}

UserService 里面添加方法 save82() ,它的事务传播类型是 REQUIRES_NEW

@Transactional(propagation = Propagation.REQUIRES_NEW)
    public void save82() {
        User user = new User();
        user.setName("82");
        user.setAge(82);
        userDao.save(user);
        int i = 1 / 0;
    }

我们现在在 site.nemo 包下新建一个类 Application8 ,来调用 UserServicesave8 方法

public class Application8 {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(TransactionConfiguration.class);
        UserService2 userService2 = context.getBean(UserService2.class);
        userService2.save8();
    }
}

从结果可以看出,save8回滚了,但是save82没有回滚。因为save82的事务传播类型是REQUIRES_NEW,它会新起一个事务,与原来的事务没关系。

原因九:标注了@Transactional的方法不是public的

可以在 UserService 里面新增一个protect方法 save9

@Transactional
    protected void save9() {
        User user = new User();
        user.setName("9");
        user.setAge(9);
        userDao.save(user);
        int i = 1 / 0;
    }

由于protect方法只能包内调用,我们可以在 site.nemo.service 里面加入一个 UserServiceManager 类,该类调用了 UserService 的protect方法

package site.nemo.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class UserManager {
    @Autowired
    private UserService userService;
    public void testUserService9() {
        userService.save9();
    }
}

我们现在在 site.nemo 包下新建一个类 Application9

public class Application9 {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(TransactionConfiguration.class);
        UserManager userManager = context.getBean(UserManager.class);
        userManager.testUserService9();
    }
}

从结果可以看出,没有回滚。

原因十:标注了@Transactional的方法发生的异常不是rollbackFor指定的类型或子类

可以在 UserService 里面新增一个方法 save10 ,它的@Transactional里指定rollbackFor为BusinessException,即发生BusinessException或它子类异常的时候才会回滚。但是我在 save10 里面抛出的是Exception类型的异常。

@Transactional(rollbackFor = BusinessException.class)
    public void save10() throws Exception {
        User user = new User();
        user.setName("10");
        user.setAge(10);
        userDao.save(user);
        throw new Exception("test save10");
    }

我们现在在 site.nemo 包下新建一个类 Application10

public class Application10 {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(TransactionConfiguration.class);
        UserService userService = context.getBean(UserService.class);
        userService.save10();
    }
}

从结果可以看出,没有回滚。

原因十一:数据库不支持事务

如果数据库引擎不支持事务的话,随便怎么加@Transactional,都不会生效。

本文就是愿天堂没有BUG给大家分享的内容,大家有收获的话可以分享下,想学习更多的话可以到微信公众号里找我,我等你哦。

相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
相关文章
|
10月前
|
SQL Java 关系型数据库
Spring事务传播机制:7种姿势教你玩转"事务接力赛"
事务传播机制是Spring框架中用于管理事务行为的重要概念,它决定了在方法调用时事务如何传递与执行。通过7种传播行为,开发者可以灵活控制事务边界,适应不同业务场景。例如:REQUIRED默认加入或新建事务,REQUIRES_NEW独立开启新事务,NESTED支持嵌套回滚等。合理使用传播机制不仅能保障数据一致性,还能提升系统性能与健壮性。掌握这“七种人格”,才能在复杂业务中游刃有余。
|
Java Spring
Spring中事务失效的场景
因为Spring事务是基于代理来实现的,所以某个加了@Transactional的⽅法只有是被代理对象调⽤时, 那么这个注解才会⽣效 , 如果使用的是被代理对象调用, 那么@Transactional会失效 同时如果某个⽅法是private的,那么@Transactional也会失效,因为底层cglib是基于⽗⼦类来实现 的,⼦类是不能重载⽗类的private⽅法的,所以⽆法很好的利⽤代理,也会导致@Transactianal失效 如果在业务中对异常进行了捕获处理 , 出现异常后Spring框架无法感知到异常, @Transactional也会失效
|
Java 关系型数据库 数据库
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——常见问题总结
本文总结了Spring Boot中使用事务的常见问题,虽然通过`@Transactional`注解可以轻松实现事务管理,但在实际项目中仍有许多潜在坑点。文章详细分析了三个典型问题:1) 异常未被捕获导致事务未回滚,需明确指定`rollbackFor`属性;2) 异常被try-catch“吃掉”,应避免在事务方法中直接处理异常;3) 事务范围与锁范围不一致引发并发问题,建议调整锁策略以覆盖事务范围。这些问题看似简单,但一旦发生,排查难度较大,因此开发时需格外留意。最后,文章提供了课程源代码下载地址,供读者实践参考。
458 0
|
Java 关系型数据库 数据库
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——Spring Boot 事务配置
本文介绍了 Spring Boot 中的事务配置与使用方法。首先需要导入 MySQL 依赖,Spring Boot 会自动注入 `DataSourceTransactionManager`,无需额外配置即可通过 `@Transactional` 注解实现事务管理。接着通过创建一个用户插入功能的示例,展示了如何在 Service 层手动抛出异常以测试事务回滚机制。测试结果表明,数据库中未新增记录,证明事务已成功回滚。此过程简单高效,适合日常开发需求。
1917 0
|
Java 数据库 微服务
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——事务相关
本文介绍Spring Boot事务配置管理,阐述事务在企业应用开发中的重要性。事务确保数据操作可靠,任一异常均可回滚至初始状态,如转账、购票等场景需全流程执行成功才算完成。同时,事务管理在Spring Boot的service层广泛应用,但根据实际需求也可能存在无需事务的情况,例如独立数据插入操作。
365 0
|
11月前
|
Java 关系型数据库 数据库
深度剖析【Spring】事务:万字详解,彻底掌握传播机制与事务原理
在Java开发中,Spring框架通过事务管理机制,帮我们轻松实现了这种“承诺”。它不仅封装了底层复杂的事务控制逻辑(比如手动开启、提交、回滚事务),还提供了灵活的配置方式,让开发者能专注于业务逻辑,而不用纠结于事务细节。
1221 1
|
SQL 存储 关系型数据库
美团面试:事务提交了,数据丢失 了 ?大概的原因是什么?
美团面试:事务提交了,数据丢失 了 ?大概的原因是什么?
美团面试:事务提交了,数据丢失 了 ?大概的原因是什么?
|
人工智能 Java 数据库连接
Spring事务失效场景
本文深入探讨了Spring框架中事务管理可能失效的几种常见场景及解决方案,包括事务方法访问级别不当、方法内部自调用、错误的异常处理、事务管理器或数据源配置错误、数据库不支持事务以及不合理的事务传播行为或隔离级别。通过合理配置和正确使用`@Transactional`注解,开发者可以有效避免这些问题,确保应用的数据一致性和完整性。
1124 10
|
Java 关系型数据库 MySQL
【Spring】【事务】初学者直呼学会了的Spring事务入门
本文深入解析了Spring事务的核心概念与使用方法。Spring事务是一种数据库事务管理机制,通过确保操作的原子性、一致性、隔离性和持久性(ACID),维护数据完整性。文章详细讲解了声明式事务(@Transactional注解)和编程式事务(TransactionTemplate、PlatformTransactionManager)的区别与用法,并探讨了事务传播行为(如REQUIRED、REQUIRES_NEW等)及隔离级别(如READ_COMMITTED、REPEATABLE_READ)。
879 1
|
Java 关系型数据库 MySQL
深入解析 @Transactional——Spring 事务管理的核心
本文深入解析了 Spring Boot 中 `@Transactional` 的工作机制、常见陷阱及最佳实践。作为事务管理的核心注解,`@Transactional` 确保数据库操作的原子性,避免数据不一致问题。文章通过示例讲解了其基本用法、默认回滚规则(仅未捕获的运行时异常触发回滚)、因 `try-catch` 或方法访问修饰符不当导致失效的情况,以及数据库引擎对事务的支持要求。最后总结了使用 `@Transactional` 的五大最佳实践,帮助开发者规避常见问题,提升项目稳定性与可靠性。
2089 12