Spring(3) 声明式事务
2023-10-30 15:01:17 # Backend # Spring

1. 声明式事务概念

编程式事务指通过编写代码的方式直接控制事务的提交和回滚。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Connection conn = ...; 

try {
// 开启事务:关闭事务的自动提交
conn.setAutoCommit(false);
// 提交事务
conn.commit();
}catch(Exception e){
// 回滚事务
conn.rollBack();
}finally{
// 释放数据库连接
conn.close();
}
  • 细节没有被屏蔽:具体操作过程中,所有细节都需要程序员自己来完成,比较繁琐。
  • 代码复用性不高:如果没有有效抽取出来,每次实现功能都需要自己编写代码,代码就没有得到复用。

声明式事务是指使用注解或 XML 配置的方式来控制事务的提交和回滚。

image-20231022151604444

Spring声明式事务对应依赖

  • spring-tx: 包含声明式事务实现的基本规范(事务管理器规范接口和事务增强等等)
  • spring-jdbc: 包含DataSource方式事务管理器实现类DataSourceTransactionManager
  • spring-orm: 包含其他持久层框架的事务管理器实现类例如:Hibernate/Jpa等

Spring声明式事务对应事务管理器接口

img

org.springframework.jdbc.datasource.DataSourceTransactionManager可用于整合 JDBC方式、JdbcTemplate方式、Mybatis方式的事务实现

DataSourceTransactionManager类中的主要方法:

  • doBegin():开启事务
  • doSuspend():挂起事务
  • doResume():恢复挂起的事务
  • doCommit():提交事务
  • doRollback():回滚事务

2. 基于注解的声明式事务

2.1 使用方法

配置类上添加 @EnableTransactionManagement, 实例化 TransactionManager 组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@Configuration
@ComponentScan("top.whalefall")
@PropertySource("classpath:jdbc.properties")
@EnableTransactionManagement
public class DataSourceConfig {
@Bean
public DataSource dataSource(@Value("${whale.url}")String url,
@Value("${whale.driver}")String driver,
@Value("${whale.username}")String username,
@Value("${whale.password}")String password){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);

return dataSource;
}

@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}

/**
* 装配事务管理实现对象
* @param dataSource
* @return
*/
@Bean
public TransactionManager transactionManager(DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
}

使用声明事务注解 @Transactional

1
2
3
4
5
6
7
@Transactional
public void changeInfo(){
studentDao.updateAgeById(100,1); // 被回滚
System.out.println("-----------"); // 这行会执行
int i = 1/0;
studentDao.updateNameById("test1",1);
}

@Transactional 标记在类上会作用在类中所有方法,设置的事务属性也会延续影响到方法执行时的事务属性

对一个方法来说,离它最近的 @Transactional 注解中的事务属性设置生效

2.2 事务属性

只读

@Transactional(readOnly = true)

超时时间

@Transactional(timeout = 3)

  • 单位为秒,默认为 -1,永不超时
  • 超时会执行回滚

事务异常

@Transactional(rollbackFor = Exception.class, noRollbackFor = FileNotFoundException.class)

  • 默认只针对运行时异常回滚,编译时异常不回滚
  • rollbackFor: 指定哪些异常类才会回滚,默认是 RuntimeException and Error 异常方可回滚
  • noRollbackFor: 指定哪些异常不会回滚, 默认没有指定, 如果指定, 应该在 rollbackFor 的范围内

事务隔离级别

@Transactional(isolation = Isolation.REPEATABLE_READ)

  • isolation: 设置事务的隔离级别, mysql默认是repeatable read

数据库事务的隔离级别是指在多个事务并发执行时,数据库系统为了保证数据一致性所遵循的规定。常见的隔离级别包括:

  1. 读未提交(Read Uncommitted):事务可以读取未被提交的数据,容易产生脏读、不可重复读和幻读等问题。实现简单但不太安全,一般不用。
  2. 读已提交(Read Committed):事务只能读取已经提交的数据,可以避免脏读问题,但可能引发不可重复读和幻读。
  3. 可重复读(Repeatable Read):在一个事务中,相同的查询将返回相同的结果集,不管其他事务对数据做了什么修改。可以避免脏读和不可重复读,但仍有幻读的问题。
  4. 串行化(Serializable):最高的隔离级别,完全禁止了并发,只允许一个事务执行完毕之后才能执行另一个事务。可以避免以上所有问题,但效率较低,不适用于高并发场景。

事务传播行为

img

1
2
3
4
5
6
7
8
9
10
11
12
@Transactional
public void MethodA(){
// ...
MethodB();
// ...
}

//在被调用的子方法中设置传播行为,代表如何处理调用的事务! 是加入,还是新事务等!
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void MethodB(){
// ...
}

@Transactional 注解通过 propagation 属性设置事务的传播行为

  • 它的默认值是 Propagation propagation() default Propagation.REQUIRED;
  • 可选值由 org.springframework.transaction.annotation.Propagation 枚举类提供
    • Propagation.REQUIRED:如果当前存在事务,则加入当前事务,否则创建一个新事务。
    1. Propagation.REQUIRES_NEW:创建一个新事务,并在新事务中执行。如果当前存在事务,则挂起当前事务,即使新事务抛出异常,也不会影响当前事务。
    2. Propagation.NESTED:如果当前存在事务,则在该事务中嵌套一个新事务,如果没有事务,则与Propagation.REQUIRED一样。
    3. Propagation.SUPPORTS:如果当前存在事务,则加入该事务,否则以非事务方式执行。
    4. Propagation.NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,挂起该事务。
    5. Propagation.MANDATORY:必须在一个已有的事务中执行,否则抛出异常。
    6. Propagation.NEVER:必须在没有事务的情况下执行,否则抛出异常。

注意

同一个类中,对于 @Transactional 注解的方法调用,事务传播行为不会生效。这是因为 Spring 框架中使用代理模式实现了事务机制,在同一个类中的方法调用并不经过代理,而是通过对象的方法调用,因此 @Transactional 注解的设置不会被代理捕获,也就不会产生任何事务传播行为的效果。