前言:
Spring的事务,也就是数据库的事务操作,符合ACID标准,也具有标准的事务隔离级别。
但是Spring事务有自己的特点,也就是事务传播机制。
所谓事务传播机制,也就是在事务在多个方法的调用中是如何传递的,是重新创建事务还是使用父方法的事务?父方法的回滚对子方法的事务是否有影响?子方法的回滚又对父方法的事务是否有影响?这些都是可以通过事务传播机制来决定的。
本文就测试一下这些事务传播机制的使用及异同
一、准备测试方法
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.7.0</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
1、配置类
@Configuration
@ComponentScan(basePackages={"com.example.demo.service"})// 扫描BlogService实现类所在的包路径
@EnableTransactionManagement //开启事务支持后,然后在访问数据库的Service方法上添加注解 @Transactional 便可
public class JdbcConfig {
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource);
}
@Bean
public DataSourceTransactionManager transactionManager(DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
@Bean
public DataSource dataSource(){
try {
return new SimpleDriverDataSource(new com.mysql.cj.jdbc.Driver(), "jdbc:mysql://localhost:3306/blog?serverTimezone=UTC", "root", "root");
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}
2、创建实体类和表(表创建读者可自定义创建)
@Data
public class Blog {
private int id;
private String name;
private String url;
public Blog(String name, String url) {
this.name = name;
this.url = url;
}
}
3、创建(BlogService和BlogService2,主要是对Blog的不同操作)
Spring默认情况下会对运行期例外(RunTimeException),即uncheck异常,进行事务回滚
这里我们对blogService2的方法进行异常捕获,因为抛出异常也会导致事务回滚,而我们主要是测试事务传播行为导致的事务回滚(父方法的回滚对子方法的事务是否有影响?子方法的回滚又对父方法的事务是否有影响?)
@Component
public class BlogService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private BlogService2 blogService2;
@Transactional(propagation= Propagation.REQUIRED)
public void save(Blog blog) {
String sql = "insert into blog(name,url) values(?,?)";
jdbcTemplate.update(sql, blog.getName(),blog.getUrl());
//对delete方法抛出的异常进行捕获处理,并且不再向上抛出
// 能保证调用者方法中的独立事务不受被调用者抛出的异常影响而回滚
try {
blogService2.delete(16);
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Component
public class BlogService2 {
@Autowired
private JdbcTemplate jdbcTemplate;
@Transactional(propagation= Propagation.SUPPORTS)
public void delete(int id){
String sql = "delete from blog where id=?";
jdbcTemplate.update(sql, id);
}
}
注意:既然要实现多事务的传播,就需要在一个方法里调用另一个类的方法,下面的测试就是基于这种方法,在BlogService的save()方法中调用BlogService2的delete()方法
5、测试
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(JdbcConfig.class);
BlogService service = ac.getBean(BlogService.class);
Blog b = new Blog("lili","www.bobo.com");
service.save(b);
}
}
二、事务传播机制的测试
1、REQUIRED(需要)
定义:如果有事务则加入事务,如果没有事务,则创建一个新的(默认值)
回滚机制:
- 如果调用方有事务,则加入,所以是同一个事务,有异常则一起回滚
- 如果调用方没事务,则创建一个新的事务
操作1:将BlogServiceImpl和BlogServiceImpl2的事务传播机制都修改为 @Transactional(propagation=Propagation.REQUIRED)
操作2:将BlogServiceImpl事务传播机制修改为@Transactional(propagation=Propagation.NOT_SUPPORTED),BlogServiceImpl2的仍为@Transactional(propagation=Propagation.REQUIRED)
结果2:
2、REQUIRES_NEW(需要新的)
定义:不管是否存在事务,都创建一个新的事务,两个事务之间没有关系
回滚机制:
- 由于调用方和被调方都属于不同的事务,所以回滚互不影响
操作:将BlogServiceImpl事务传播机制修改为@Transactional(propagation=Propagation.REQUIRED),BlogServiceImpl2的仍为@Transactional(propagation=Propagation.REQUIRES_NEW)
结果:
3、SUPPORTS(支持)
定义:调用方有事务则直接用,如果没有则不使用事务
回滚机制:
- 调用方有事务则加入,由于是同一个事务,一旦发生异常,则整体回滚
- 调用方没事务,被调方也不会使用事务,则不存在回滚
操作1:将BlogServiceImpl事务传播机制修改为@Transactional(propagation=Propagation.REQUIRED),BlogServiceImpl2的仍为@Transactional(propagation=Propagation.SUPPORTS)
结果1:
操作2:将BlogServiceImpl事务传播机制修改为@Transactional(propagation=Propagation.NOT_SUPPORTED),BlogServiceImpl2的仍为@Transactional(propagation=Propagation.SUPPORTS)
结果2:
4、NOT_SUPPORTED(不支持)
定义:Spring不为当前方法开启事务,相当于没有事务
回滚机制:
- 不论调用方是否有事务,被调方都不使用事务,则不存在回滚
操作:将BlogServiceImpl和BlogServiceImpl2的事务传播机制都修改为@Transactional(propagation=Propagation.NOT_SUPPORTED)
结果:
5、NEVER(从不)
定义:必须在一个没有的事务中执行,否则报错
回滚机制:
- 如果调用方开启了事务,则被调方会抛出异常,被调方回滚
操作:将BlogServiceImpl事务传播机制修改为@Transactional(propagation=Propagation.REQUIRED),BlogServiceImpl2的仍为@Transactional(propagation=Propagation.NEVER),查看是否报错
结果:
6、MANDATORY(强制)
定义:必须在一个已有的事务中执行,否则报错
回滚机制:
- 如果调用方开启事务,则加入同一个事务,不论谁异常,都会整体回滚
- 如果调用方没开启事务,则抛出异常,被调方回滚
操作:将BlogServiceImpl事务传播机制修改为@Transactional(propagation=Propagation.NOT_SUPPORTED),BlogServiceImpl2的仍为@Transactional(propagation=Propagation.MANDATORY),查看是否报错
结果:
7、NESTED(嵌套)
定义:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与REQUIRED类似的操作
若调用者方法有开启事务。此时NESTED会开始一个 "嵌套的" 事务, 它是已经存在事务的一个真正的子事务。 嵌套事务开始执行时, 它将取得一个 savepoint
。 如果这个嵌套事务失败, 我们将回滚到此 savepoint
。 嵌套事务是外部事务的一部分, 只有外部事务结束后它才会被提交。
回滚机制:
- 主事务和嵌套事务属于同一个事务
- 嵌套事务出错回滚不会影响到主事务
- 主事务回滚会将嵌套事务一起回滚了
操作1:将BlogServiceImpl事务传播机制修改为@Transactional(propagation=Propagation.REQUIRED),BlogServiceImpl2的仍为@Transactional(propagation=Propagation.NESTED)
结果1:
操作2:将BlogServiceImpl事务传播机制修改为@Transactional(propagation=Propagation.NOT_SUPPORTED),BlogServiceImpl2的仍为@Transactional(propagation=Propagation.NESTED)
结果2: