Spring 事务

本文最后更新于 2025年6月24日 晚上

什么是事务

事务,是系统在处理业务时,为保障数据一致性、完整性与可靠性而设计的一种逻辑单元抽象。它源于现实中的业务流程抽象,如:

1
2
复制编辑
客户发起请求 —> 供应商响应请求

但在现代系统中,业务请求通常会被分解为一系列跨越多个系统组件的处理过程,例如:

1
2
3
4
5
6
7
8
rust
复制编辑
Customer(请求)
-> Web代理
-> 应用服务
-> 缓存服务器
-> 消息队列
-> 数据库服务

事务的目标就是在这种复杂系统流转中,确保数据的 正确性和一致性

事务的 ACID 特性

特性 说明
原子性(Atomicity) 要么全部成功,要么全部失败
一致性(Consistency) 事务执行前后,系统状态合法且符合业务规则
隔离性(Isolation) 多个事务互不干扰,互不影响
持久性(Durability) 事务提交后的结果永久保存

不同组件中的事务处理机制

代理服务与缓存服务器

  • 代理层 :一般只做请求转发,不参与事务控制;
  • 缓存层 :通常处理静态数据,对核心业务一致性要求低,事务性不强,重点在于 缓存一致性与失效策略

消息队列中的事务处理

  • 主要通过可靠投递机制保障最终一致性;
  • 通过 事务消息机制 (如RocketMQ)支持本地事务与消息状态协调;
  • 通过幂等设计、重复消费保护提升整体稳定性。

以 RocketMQ 为例的事务消息流程:

  1. 发送“预处理消息”到MQ。
  2. 执行本地事务(如数据库写入)。
  3. 根据事务执行结果向MQ提交 commit 或 rollback。
  4. MQ端若长时间未收到确认,将主动发起事务状态回查。

应用服务器中的事务管理

  • 通过应用框架提供的事务管理器(如Spring @Transactional)划定事务边界;
  • 提供事务传播机制(如REQUIRED、REQUIRES_NEW等)管理嵌套事务;
  • 支持跨资源事务管理(如JTA、XA协议)协调多数据库或外部资源的事务一致性。

数据库中的事务支持

  • 通过事务日志(如WAL)、Redo Log、Undo Log保障持久性;
  • 通过锁机制(行锁、表锁)、隔离级别保障并发控制;
  • 支持 savepoint、rollback、commit 等事务控制语句。

业务流程中的事务处理

使用DataSource 完成与SQL的通信

在SpringBoot中,定义DataSource 参数

1
2
3
4
5
spring.application.name=aot
spring.datasource.url=jdbc:mysql://localhost:3306/springboot
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

直接可以得到数据库连接对象

1
2
3
4
@Test
void contextLoads(@Autowired DataSource datasource) throws Throwable{
System.out.println(datasource.getConnection());
}

在Spring中需要配置连接类,定义dataSource类

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
37
38
39
package com.codfish.aot.spring;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

import javax.sql.DataSource;

@Configuration
public class DataSourceConfig {
@Value("${spring.datasource.url}")
private String url ;
@Value("${spring.datasource.username}")
private String username ;
@Value("${spring.datasource.password}")
private String password ;
@Value("${spring.datasource.driver-class-name}")
private String driverClassName ;

@Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
dataSource.setDriverClassName(driverClassName);
return dataSource;
}


}

@Test
void contextLoads2(@Autowired DataSourceConfig datasource) throws Throwable{
System.out.println(datasource.dataSource());
}

创建JDBCTemplate 完成数据库命令执行

SpringBoot :

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
package com.codfish.aot.spring;

public class User {
private String name;
private Integer id;
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
public Integer getId(){
return id;
}
public void setId(Integer id){
this.id = id;
}

@Override
public String toString() {
return "User{ name=" +name+ " ID=" + id + '}';
}
}
// 创建一个类完成数据接收

@Test
void testJdbctemplate(@Autowired JdbcTemplate jdbcTemplate) throws SQLException {
// jdbcTemplate.update("insert into student(ID,NAME) values(?,?)",2,"codfish");
String sql = "select * from student where id=?";
User user = jdbcTemplate.queryForObject(sql,new BeanPropertyRowMapper<>(User.class),2);
System.out.println(user);
}

Spring :

1
2
3
4
5
6
7
8
9
10
11
12
13
// 创建jdbcTemplate bean
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource);
}
// 执行查询
@Test
void testJdbctemplate(@Autowired JdbcTemplate jdbcTemplate) throws SQLException {
// jdbcTemplate.update("insert into student(ID,NAME) values(?,?)",2,"codfish");
String sql = "select * from student where id=?";
User user = jdbcTemplate.queryForObject(sql,new BeanPropertyRowMapper<>(User.class),2);
System.out.println(user);
}

事务

SpringBoot : 直接向需要使用事务的方法加入@Transactional注解

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
@Repository
public class UserDao {
@Autowired
JdbcTemplate jdbcTemplate;
public int insert (){
return jdbcTemplate.update("insert into student(ID,NAME) values(?,?)",3,"godfish");
}
public int update (){
return jdbcTemplate.update("update student set NAME=? where ID=?", "codfish",1);
}
public int delete (){
return jdbcTemplate.update("delete from student where ID=?",1);
}
public User query (){
return jdbcTemplate.queryForObject("select * from student where ID=?",
new BeanPropertyRowMapper<>(User.class),1);
}

} // 数据类 定义 查询命令和方法

@Service
public class UserService {
@Autowired
UserDao userDao;
@Transactional
public int insert ()
{
return userDao.insert();
}
} // 服务类进行执行

在Spring中 需要启动EnableTransactionManagement 注解

1
@EnableTransactionManagement 

并在业务中定义一个TransactionManager的 Bean

1
2
3
4
@Bean
public TransactionManager transactionManager(DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}

事务的隔离级别

在多线程数据访问过程中,多个查询事务可以会存在相互间的冲突,导致数据存在不一致的情况

丢失更新 脏读 不可重复读 幻读
Read Uncommit x
Read Commited x x 通过行锁在事务期间锁定读操作
Repeatable Read x x x 通过行锁在事务期间锁定读写
Serializable x x x x 通过表锁在事务操作期间锁定

事务传播

在业务过程中存在某个方法被定义为事务之后其调用的后续方法也通过事务执行,这时通过设置不同的isolation 来控制 多个事务之间的行为

外部请求不存在事务 外部请求存在事务
REQUIRED 开启新的事务 融入外部事务 @Transactional(propagation=Progagation.REQUIRED) 增删改查
SUPPORTS 不开启新的事务 融入外部事物 @Transactional(propagation=Progagation.SUPPORT) 适合查询
REQUIRES_NEW 开启新的事务 开启新的事务 @Transactional(propagation=Progagation.REQUIRES_NEW) 适用于与外部事务无关的情况
NOT_SUPPORT 不开启新的事务 不用外部事务 @Transactional(propagation=Progagation.NOT_SUPPORT)
NEVER 不开启新的事务 抛出异常 @Transactional(propagation=Progagation.NEVER)
MANDATORY 抛出异常 融合到外部事务中 @Transactional(propagation=Progagation.MANDATORY)
NESTED 开启新的事务 融合到外部事务中,SAVEPOINT @Transactional(propagation=Progagation.NESTED)

readOnly

只会设置在查询的业务方法中,具体执行效率视数据库类型而定

timeout

设置事务等待的最长时间

noRollbackFor / RollbackFor

决定哪些 异常会回滚,哪些异常不会回滚

默认RuntimeException 及其子类异常会回滚

事务失效

  1. 保证事务的类配置为一个bean
  2. 事务方法不能是一个private
  3. 事务执行期间的异常被自行捕捉
  4. 只有动态代理过的对象才可以正常执行事务

解决 1. 暴露自身包装为动态代理对象(循环依赖)

1
2
3
2. AopContext.currentProxy()

3. 创建一个Bean ,执行DAO对象 再将该对象引用进来执行

本质原因在于 Spring AOP 是基于代理机制实现事务增强的:自调用绕过代理、异常捕获、非 public 方法、未被 Spring 管理的对象均绕过了事务代理逻辑。


Spring 事务
http://gadoid.io/2025/06/24/Spring-事务/
作者
Codfish
发布于
2025年6月24日
许可协议