Spring对事务的支持
事务概述
事务:Transaction(tx),是业务逻辑中的最小单元,不可再分,所有的DML必须同时成功或全部失败并回滚。
事务的四个处理过程:
- 开启事务 (start transaction)
- 执⾏核⼼业务代码
- 提交事务(如果核⼼业务处理过程中没有出现异常)(commit transaction)
- 回滚事务(如果核⼼业务处理过程中出现异常)(rollback transaction)
事务的四个特性:
- A 原⼦性 Atomicity:事务是最⼩的⼯作单元,不可再分。
- C ⼀致性 Consistency:事务要求要么同时成功,要么同时失败。事务前和事务后的总量不变。
- I 隔离性 Isolation:事务和事务之间因为有隔离性,才可以保证互不⼲扰。
- D 持久性 Durability:持久性是事务结束的标志。
Spring 对事务的支持
Spring实现事务的两种⽅式:
- 编程式事务:通过编写代码的⽅式来实现事务的管理(如 15-AOP案例-事务管理)。
- 声明式事务
- 基于注解方式
- 基于xml配置方式
Spring 提供了专门的一套API来控制事务,底层基于AOP实现,采用AOP进行了代码封装,API 核心接口如下:

PlatformTransactionManager接口:spring事务管理器的核心接口。在Spring6中它有两个实现:
- DataSourceTransactionManager:支持 JdbcTemplate、MyBatis、Hibernate 等事务管理。
- JtaTransactionManager:支持分布式事务管理。
以使用 JdbcTemplate 控制事务为例,使用的事务管理器是 DataSourceTransactionManager。
<!--spring context-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.0-M2</version>
</dependency>
<!--spring jdbc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>6.0.0-M2</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<!--德鲁伊连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.13</version>
</dependency>
<!--@Resource注解-->
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>2.1.1</version>
</dependency>事务控制的相关属性
不论是基于注解还是使用xml,事务控制原理都是一样的。

重要的属性:
- 事务传播行为 propagation
- 事务隔离级别 isolation
- 事务超时 timeout
- 只读事务 readOnly
- 需要回滚的异常 rollbackFor
- 不需要回滚的异常 noRollbackFor
传播行为
在Spring框架中,事务的传播行为 propagation 是指在多个事务方法相互调用的情况下,事务应该如何传播。
// @Transactional
... you(){
// ...
// call me
me();
// ...
}
// @Transactional
... me(){
// ...
}7种事务传播行为:
REQUIRED(默认):如果当前存在事务,则加入到当前事务中进行处理,否则新建一个事务。
(你有用你的,你没我就用自己的)
SUPPORTS:如果当前存在事务,则加入到当前事务中进行处理,否则以非事务方式执行。
(你说啥就是啥,你有我就加⼊,没有我也不管)
MANDATORY:必须在一个事务中运行,如果当前没有事务,则抛出异常。
(你有就加⼊,没有我不干了)
REQUIRES_NEW:开启⼀个新的事务,如果⼀个事务已经存在,则将这个存在的事务挂起。
(管你有没有,我就用自己的)
NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,则将其挂起。
(管你有没有,我就要罗本)
NEVER:以非事务方式执行操作,如果当前存在事务,则抛出异常。
(我不准你有,你有我就要闹了)
NESTED:如果当前存在事务,则在一个嵌套的事务中执行(嵌套的事务可以独立地提交和回滚);如果当前没有事务,则新建一个事务。
(你有我就偷偷地,你没我就大方地)
设置事务的传播行为:
// 使用 Propagation 枚举
@Transactional(propagation = Propagation.REQUIRED)隔离级别
数据库中读取数据存在三大读问题:
- 脏读:读取到没有提交到数据库的数据。
- 不可重复读:在同⼀个事务当中,前后两次读取的数据不⼀样。
- 幻读:读到的数据是假的,不再在数据库里了。
- 只要事务并发,就一定存在幻读的问题。
- 如果事务不支持并发,就不会存在幻读问题。
事务的隔离级别 isolation 决定了不同事务之间的数据共享问题,一定程度上可以解决三大读问题:
- 读未提交 READ_UNCOMMITTED:别的事务还没有提交,存储在缓存中的数据也能读。存在脏读问题。
- 读提交 READ_COMMITTED:别的事务只有提交了才能读取到,解决了脏读问题。
- 可重复读 REPEATABLE_READ:只要当前事务不结束,读取到的数据⼀直都是⼀样的。解决了不可重复读,可以达到可重复读效果,但存在幻读问题。
- 序列化 SERIALIZABLE:不支持并发,事务必须排队执行。解决了幻读问题。
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| 读未提交 | 有 | 有 | 有 |
| 读提交 | 无 | 有 | 有 |
| 可重复读 | 无 | 无 | 有 |
| 序列化 | 无 | 无 | 无 |
指定事务的隔离级别:
// 使用 Isolation 枚举
@Transactional(isolation = Isolation.READ_COMMITTED)事务超时
// 单位是 秒s
@Transactional(timeout = 10)
// 指定事务超时时间为10秒
// -1 表示不限制时间如何判断超时?
事务开始时计时,每当执行一条DML语句时记录耗时,如果耗时超过了指定时长timeout,则判定为超时。
底层采用观察者模式(Observer/Listener)实现。
简单认为 从 开始事务 到 执行最后一条DML语句 的时间记为耗时。
如果想让整个⽅法的所有代码都计⼊超时时间的话,可以在⽅法最后⼀⾏添加⼀⾏⽆关紧要的DML语句。
只读事务
@Transactional(readOnly = true)将当前事务设置为只读事务,在该事务执⾏过程中只允许select语句执⾏,delete insert update均不可执⾏。
设置这个属性,是为了启动spring的优化策略,提高select语句执行效率。
声明式事务+注解
将事务管理器交给Spring管理
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!--注入数据源,如德鲁伊--> <property name="dataSource" ref="dataSource"/> </bean>引入 tx 命名空间:
xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation = "... http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"配置“事务注解驱动器”,并指明以注解的⽅式控制事务
<!--annotation 表示使用注解控制事务--> <tx:annotation-driven transaction-manager="transactionManager"/>在需要事务的地方加上 @Transactional 注解
在类上添加该注解,表示该类中所有的⽅法都有事务。
在某个⽅法上添加该注解,表示只有这个⽅法使⽤事务。.
全注解式开发
一样,提供一个类,使用注解标注配置信息。使用 AnnotationConfigApplicationContext 类解析获得配置信息。
// 标注从这个类获取配置信息
@Configuration
// 开启包扫描
@ComponentScan("com.powernode.bank")
// 启用事务管理器
@EnableTransactionManagement
public class Spring6Config {
// 使用 @Bean + getter 托管对象至Spring
// 方法名无关,只要被 @Bean 标注都会执行并将返回值作为 Bean
// 可以使用 name 属性指定 bean 的id
// 有参数时支持自动注入
@Bean
public DataSource getDataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/spring6");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
@Bean(name = "jdbcTemplate")
public JdbcTemplate getJdbcTemplate(DataSource dataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
}声明式事务+xml
额外引入 spring-aspects 依赖
<!--aspectj依赖--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>6.0.0-M2</version> </dependency>启用 aop 命名空间,配置数据源
配置事务管理器
<!--配置事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean>配置通知
<!--配置通知--> <tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <tx:method name="save*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/> <tx:method name="del*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/> <tx:method name="update*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/> <tx:method name="transfer*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/> </tx:attributes> </tx:advice>配置切面
<!--配置切⾯--> <aop:config> <aop:pointcut id="txPointcut" expression="execution(* com.powernode.bank.service..*(..))"/> <!--切⾯ = 通知 + 切点--> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/> </aop:config>
