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

这篇笔记来学习一下使用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的基础上。

TransactionConfiguration 配置文件如下,需要实例化TransactionManager 
import javax.sql.DataSource;

@Configuration
@EnableTransactionManagement
@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://121.43.178.191:3306/springshiwu?characterEncoding=utf8");
        dataSource.setUsername("root");
        dataSource.setPassword("abcd1234");

        return dataSource;
    }

    @Bean
    public TransactionManager TransactionManager(DataSource dataSource){
        return  new DataSourceTransactionManager(dataSource );
    }
}

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

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

 @Transactional
    public void save2() {
        try {
            User user = new User();
            user.setName("1");
            user.setAge(1);

            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();
        }
    }
}

 

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


原因四:标注了@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方法

    @Transactional(propagation = Propagation.NEVER)
    public void save5() {
        User user = new User();
        user.setName("5");
        user.setAge(5);

        userDao.save(user);

        int i = 1 / 0;
    }

从结果可知,没有回滚。


原因六:标注了@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,它会新起一个事务,与原来的事务没关系。如果放行了save82方法中的in i=1/0,那么两个方法都会回滚


原因九:标注了@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();
    }
}
123456789101112131415

我们现在在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();
    }
}
12345678

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


原因十:标注了@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,都不会生效。😂

 

posted @ 2021-07-10 18:42  小陈子博客  阅读(765)  评论(0编辑  收藏  举报