目录
前言
Spring Framework 提供了全面的事务管理功能,以确保在应用程序中操作的原子性、一致性、隔离性和持久性(即 ACID 特性)。
事务管理是重要的,尤其是在涉及到数据库操作时,以确保数据的完整性和一致性。
1、介绍
1.1、底层构造
Spring事务管理高层抽象主要包括3个接口,Spring的事务主要是由他们共同完成的:
PlatformTransactionManager:事务管理器—主要用于平台相关事务的管理
TransactionDefinition:事务定义信息(隔离、传播、超时、只读)—通过配置如何进行事务管理。
TransactionStatus:事务具体运行状态—事务管理过程中,每个时间点事务的状态信息。
1.1.事务管理器
PlatformTransactionManager该接口提供三个方法:
- commit:提交事务
- rollback:回滚事务
- getTransaction:获取事务状态
1.2.事务定义信息
TransactionDefinition该接口主要提供的方法:
getIsolationLevel:隔离级别获取
getPropagationBehavior:传播行为获取
getTimeout:获取超时时间
isReadOnly 是否只读(保存、更新、删除—对数据进行操作-变成可读写的,查询-设置这个属性为true,只能读不能写)
1.3.事务状态
TransactionStatus代表获取事务的状态。
try {
操作
} catch (){
rollback
} finally {
commit
}
1.4.联系:
用户管理事务,先配置事务管理方案TransactionDefinition、管理事务通过TransactionManager完成,TransactionManager根据 TransactionDefinition进行事务管理,
在事务运行过程中,每个时间点都可以通过获取TransactionStatus了解事务运行状态!
1.2、特点
定义
- 事务是一个操作序列,这些操作要么全部成功,要么全部失败。无法将部分操作视为成功,其他部分则视为失败。因此,事务是一种确保数据一致性和完整性的重要手段。
ACID 特性
- 原子性(Atomicity): 事务内的所有操作要么全部成功,要么全部失败。
- 一致性(Consistency): 事务必须使数据库从一个一致的状态转变到另一个一致的状态。
- 隔离性(Isolation): 一个事务的执行不能被其他事务干扰,确保并发执行下的每个事务都是在其自己的上下文中执行。
- 持久性(Durability): 事务一旦提交,其结果便永久保存,即使系统故障也不会丢失。
1.3、原理
常见面试题:事务的原理
Spring 事务的底层实现主要使用的技术:AOP(动态代理) + ThreadLocal + try/catch。
1.动态代理AOP:
基本所有要进行逻辑增强的地方都会用到动态代理,AOP 底层也是通过动态代理实现。
2.事务同步管理器(TransactionSynchronizationManager)
Spring使用线程局部变量(ThreadLocal)来管理事务资源:
ThreadLocal:主要用于线程间的资源隔离,以此实现不同线程可以使用不同的数据源、隔离级别等等。
public abstract class TransactionSynchronizationManager {
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");
private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
new NamedThreadLocal<>("Transaction synchronizations");
private static final ThreadLocal<String> currentTransactionName =
new NamedThreadLocal<>("Current transaction name");
private static final ThreadLocal<Boolean> currentTransactionReadOnly =
new NamedThreadLocal<>("Current transaction read-only status");
private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
new NamedThreadLocal<>("Current transaction isolation level");
private static final ThreadLocal<Boolean> actualTransactionActive =
new NamedThreadLocal<>("Actual transaction active");
}
3.try/catch:
最终是执行 commit 还是 rollback,是根据业务逻辑处理是否抛出异常来决定。
Spring 事务的核心逻辑伪代码如下:
public void invokeWithinTransaction() {
// 1.事务资源准备
try {
// 2.业务逻辑处理,也就是调用被代理的方法
} catch (Exception e) {
// 3.出现异常,进行回滚并将异常抛出
} finally {
// 现场还原:还原旧的事务信息
}
// 4.正常执行,进行事务的提交
// 返回业务逻辑处理结果
}
4.流程
@Transactional方法调用
│
↓
TransactionInterceptor拦截
│
↓
获取事务属性(@Transactional配置)
│
↓
根据传播行为决定是否创建新事务
│
↓
TransactionSynchronizationManager绑定资源到当前线程
│
↓
执行业务方法
│
┌─────────┴──────────┐
↓ ↓
方法成功 方法抛出异常
│ │
↓ ↓
提交事务 判断是否需要回滚
│ │
↓ ↓
清理线程资源 回滚事务
│ │
↓ ↓
返回结果 抛出异常
2. Spring 的事务管理
Spring 提供了强大的事务管理功能,包括编程式和声明式事务管理。
1、声明式事务管理
(xml和注解形式)
特点
- 易于使用:只需在方法上添加注解或在配置文件中定义事务规则即可启用事务管理。
- 非侵入性:事务管理逻辑与业务逻辑分离,不会干扰业务逻辑代码。
- 可配置性:可以灵活地配置事务的传播行为、隔离级别、超时和回滚规则等。
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@Transactional // 默认传播行为为 REQUIRED
public void createUser(String username) {
System.out.println("User " + username + " created.");
// 调用另一个方法
addUser(username);
}
@Transactional(propagation = Propagation.REQUIRES_NEW) // 新事务
public void addUser(String username) {
System.out.println("User " + username + " added in a new transaction.");
}
@Transactional(propagation = Propagation.NESTED) // 嵌套事务
public void updateUser(String username) {
System.out.println("Updating user: " + username);
}
}
输出:
在调用 createUser 方法之后,如果内部调用 addUser,因为 addUser 不会使用aop代理,
这也意味着,addUser 的成功或失败不会影响到 createUser 所在的原有事务。
2、编程式事务
通过TransactionTemplate手动管理事务。这里不过多讲解,实际使用较少。
在代码中显式地管理事务的开始、提交和回滚。这种方式更加灵活,但同时也增加了代码的复杂性。
有兴趣的可以参考:
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private PlatformTransactionManager transactionManager;
public void createUser(User user) {
TransactionDefinition def = new DefaultTransactionDefinition();
TransactionStatus status = transactionManager.getTransaction(def);
try {
userRepository.save(user);
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
throw e;
}
}
}
和JDBC相比:
不需要开始或是关闭connection(可能还有try-finally的语法),相对应的,使用的是Transaction callbacks。
也不需要手动的catch SQLExceptions,因为Spring会把这个异常当作runtime exceptions来处理。
当然想要使用TransactionTemplate,需要先设置TransactionManager,并需要传入dataSource。这些都可以配置成Spring的bean,放到context中。
3、事务传播行为
Spring 提供了多种事务传播行为来定义一个事务方法应如何参与现有事务。常用的传播行为包括:
对事务比较积极的三个传播行为:
- REQUIRED: 使用现有的事务,如果没有,则创建一个新的事务(默认值)。
- REQUIRES_NEW: 总是创建一个新的事务,并挂起当前事务。
- MANDATORY: 必须存在一个当前事务,若没有则抛出异常。
观望者:
- NESTED: 在当前事务中执行一个嵌套事务(支持保存点)。
比较佛系的三个传播行为
- SUPPORTS: 存在则加入,不存在,则非事务运行。
- PROPAGATION_NOT_SUPPORTED:不能参与。
- PROPAGATION_NEVER:不参与,参与事务,则抛出异常。
3、事务的隔离级别
1、并发事务的问题
2、隔离级别
3、如何设置
(1) 声明式事务(通过 @Transactional
注解)
@Transactional(isolation = Isolation.READ_COMMITTED)
public void transferMoney() {
// 业务逻辑
}
(2) 编程式事务(通过 TransactionTemplate
)
@Autowired
private TransactionTemplate transactionTemplate;
public void executeInTransaction() {
transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_REPEATABLE_READ);
transactionTemplate.execute(status -> {
// 业务逻辑
return null;
});
}
4、选择隔离级别
-
优先使用默认值(ISOLATION_DEFAULT)
除非有明确需求,否则依赖数据库默认配置,避免框架与数据库行为不一致。 -
平衡性能与一致性
- 高并发场景 →
READ_COMMITTED
(减少锁竞争)。 - 强一致性场景 →
REPEATABLE_READ
或SERIALIZABLE
(需测试性能影响)。
- 高并发场景 →
5、查看隔离级别
- 数据库日志:执行
SHOW VARIABLES LIKE 'transaction_isolation';
(MySQL)或等效命令。 - 代码调试:通过
TransactionSynchronizationManager.getCurrentTransactionIsolationLevel()
获取。
4、事务失效
1. 直接调用同一类中的事务方法
在同一类中直接调用被事务注解的方法会导致事务失效。因为 Spring 的 AOP 代理只在通过 Spring 管理的 Bean 进行调用时才会生效。
代码示例:
@Service
public class UserServiceTxImpl implements UserServiceTx {
@Transactional(rollbackFor = RuntimeException.class) // 默认传播行为为 REQUIRED
public void createUser(String username) {
System.out.println("User " + username + " created.");
// 调用另一个方法
addUser(username);
System.out.println("User " + username + " createUsed ");
}
@Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = RuntimeException.class) // 新事务
public void addUser(String username) {
System.out.println("User " + username + " added in a new transaction.");
throw new RuntimeException("Exception in innerMethod");
}
}
解决方案
1、可以在本bean里面直接使用spring注入的方式:不过会导致循环依赖。
2、 AopContext.currentProxy();
@Service
public class UserServiceTxImpl implements UserServiceTx {
@Autowired
private UserServiceTxImpl userService;
@Transactional(rollbackFor = RuntimeException.class) // 默认传播行为为 REQUIRED
public void createUser(String username) {
System.out.println("User " + username + " created.");
// 调用另一个方法
userService.addUser(username);
System.out.println("User " + username + " createUsed ");
}
@Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = RuntimeException.class) // 新事务
public void addUser(String username) {
System.out.println("User " + username + " added in a new transaction.");
throw new RuntimeException("Exception in innerMethod");
}
}
UserServiceTx o = (UserServiceTx) AopContext.currentProxy();
2. 直接使用 SQL 操作
如果你在事务管理的方法中执行了原生的 SQL 操作(例如通过 JDBC),而没有通过 Spring 的事务管理进行处理,事务可能会失效。
@Service
public class UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Transactional
public void createUser(String username) {
System.out.println("In createUser method");
// 直接使用 JDBC 操作,不通过 Spring 数据库访问
jdbcTemplate.execute("INSERT INTO users (username) VALUES ('" + username + "')");
// 模拟异常
throw new RuntimeException("Intentional exception"); // 模拟抛出异常
}
}
这些操作不通过 Spring 的数据访问层,将会导致事务管理操作失效。
3. 使用不正常的异常处理
Spring 只对运行时异常(RuntimeException
)自动回滚,而对检查性异常(Exception
)默认不回滚。
如果需要回滚需要rollbackFor指定回滚异常。
@Transactional(rollbackFor = RuntimeException.class) // 默认传播行为为 REQUIRED
public void createUser(String username) {
System.out.println("User " + username + " created.");
// 调用另一个方法
userService.addUser(username);
System.out.println("User " + username + " createUsed ");
}
4、非public方法,目前事务 只支持pub
5、mysql的存储引擎说mysaim不支持事务。
6、对异常进行try catch捕获,导致无法生效。
7、事务处理的响应超时。
总结:
Spring 的事务管理功能为开发者提供了一种高效且灵活的方式来处理复杂的数据库操作,确保数据的一致性和完整性,通过注解或 XML 配置,可以很容易地查看哪些方法是事务性的,逻辑结构清晰。
参考文章: