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
,来调用UserService
的save1
方法
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
,来调用UserService
的save2
方法
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
,来调用UserService
的save3
方法
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
,来调用UserService
的save4
方法
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
,来调用UserService
的save5
方法
@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
,来调用UserService
的save6
方法
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
,来调用UserService
的save7
方法
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()
,它的事务传播类型是REQUIRED
。UserService2
有一个属性是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
,来调用UserService
的save8
方法
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();
}
}
我们现在在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,都不会生效。😂
本文来自博客园,作者:小陈子博客,转载请注明原文链接:https://www.cnblogs.com/cj8357475/p/14994518.html