事务管理详解
# 事务管理
在企业级应用中,事务管理是保障数据一致性的重要手段。Spring Framework 提供了强大的事务管理功能,而若依框架则通过整合 Spring Boot 简化了事务管理的配置和使用。本文将详细介绍如何在若依框架中实现和使用事务管理,并涵盖常见的使用场景、注意事项以及可能遇到的坑点。
# 一、Spring Boot 项目中的事务管理
在一个标准的 Spring Boot 项目中,通常会引入 spring-boot-starter
或 spring-boot-starter-web
依赖。这些依赖包已经默认包含了对 spring-boot-starter-jdbc
或 spring-boot-starter-data-jpa
的依赖。因此,框架会自动为你配置 DataSourceTransactionManager
(用于 JDBC)或 JpaTransactionManager
(用于 JPA)作为事务管理器。
这意味着你可以直接在方法或类上使用 @Transactional
注解来管理事务,而无需额外的配置。
# 二、@Transactional
注解的基本使用
@Transactional
注解用于声明方法或类中的事务行为。该注解通常应用于 public
可见度的方法上,可以应用于接口定义和接口方法。方法上的 @Transactional
注解会覆盖类级别的事务声明。
示例: 用户新增时,插入用户表、用户与岗位关联表、用户与角色关联表的数据。为了确保数据的一致性,可以使用事务管理来实现,如果中间任何一步失败,则回滚所有操作。
@Transactional
public int insertUser(User user) {
// 新增用户信息
int rows = userMapper.insertUser(user);
// 新增用户岗位关联
insertUserPost(user);
// 新增用户与角色关联
insertUserRole(user);
return rows;
}
2
3
4
5
6
7
8
9
10
在上面的例子中,如果 insertUserPost
或 insertUserRole
方法抛出异常,整个事务将被回滚,已插入的用户信息也会被撤销。
# 三、事务回滚机制
Spring 的默认事务回滚规则是:仅在遇到 RuntimeException
或 Error
时才回滚事务。如果希望在遇到其他类型的异常时也回滚事务,可以通过 rollbackFor
属性指定异常类型。
示例: 在遇到 SQLException
时回滚事务。
@Transactional(rollbackFor = Exception.class)
public int insertUser(User user) throws Exception {
// 新增用户信息
int rows = userMapper.insertUser(user);
// 新增用户岗位关联
insertUserPost(user);
// 新增用户与角色关联
insertUserRole(user);
// 模拟抛出 SQLException 异常
boolean flag = true;
if (flag) {
throw new SQLException("发生异常了..");
}
return rows;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
在上述代码中,rollbackFor = Exception.class
确保了即使遇到 SQLException
这样的检查异常,事务也会回滚。
# 四、常见事务管理坑点
# 1. 检查异常不回滚
默认情况下,Spring 只在 RuntimeException
或 Error
时回滚事务。如果你的代码中抛出的是检查异常(如 SQLException
),事务将不会回滚。要确保事务在检查异常发生时也回滚,可以使用 rollbackFor
属性。
# 2. 业务层捕获异常导致事务失效
在业务层捕获异常并处理后,事务不会回滚,因为 Spring 无法感知到异常的发生。
错误示例:
@Transactional
public int insertUser(User user) throws Exception {
// 新增用户信息
int rows = userMapper.insertUser(user);
// 新增用户岗位关联
insertUserPost(user);
// 新增用户与角色关联
insertUserRole(user);
try {
// 模拟抛出 SQLException 异常
throw new SQLException("发生异常了..");
} catch (Exception e) {
e.printStackTrace(); // 异常被捕获并处理,事务不会回滚
}
return rows;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
推荐做法: 在业务层统一抛出异常,让控制层来处理异常,从而确保事务回滚。
@Transactional
public int insertUser(User user) throws Exception {
// 新增用户信息
int rows = userMapper.insertUser(user);
// 新增用户岗位关联
insertUserPost(user);
// 新增用户与角色关联
insertUserRole(user);
// 模拟抛出 RuntimeException 异常
boolean flag = true;
if (flag) {
throw new RuntimeException("发生异常了..");
}
return rows;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 五、@Transactional
注解的常用属性
属性 | 说明 |
---|---|
propagation | 事务的传播行为,默认值为 REQUIRED 。 |
isolation | 事务的隔离级别,默认值为 DEFAULT 。 |
timeout | 事务的超时时间,默认值为 -1 ,表示不超时。如果超过该时间限制但事务还未完成,则回滚事务。 |
read-only | 指定事务是否为只读事务,默认值为 false 。对于只读取数据的方法,可以将其设置为 true 。 |
rollbackFor | 用于指定触发事务回滚的异常类型,可以指定多个异常类型,之间用逗号分隔。 |
noRollbackFor | 指定不触发事务回滚的异常类型,多个异常类型之间用逗号分隔。 |
# 六、事务传播机制
事务传播机制指定了当一个事务性方法调用另一个事务性方法时,如何处理事务。常用的传播行为如下:
常量 | 含义 |
---|---|
TransactionDefinition.PROPAGATION_REQUIRED | 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。 |
TransactionDefinition.PROPAGATION_REQUIRES_NEW | 创建一个新的事务,如果当前存在事务,则把当前事务挂起。 |
TransactionDefinition.PROPAGATION_SUPPORTS | 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。 |
TransactionDefinition.PROPAGATION_NOT_SUPPORTED | 以非事务方式运行,如果当前存在事务,则把当前事务挂起。 |
TransactionDefinition.PROPAGATION_NEVER | 以非事务方式运行,如果当前存在事务,则抛出异常。 |
TransactionDefinition.PROPAGATION_MANDATORY | 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。 |
TransactionDefinition.PROPAGATION_NESTED | 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则等同于 REQUIRED 。 |
通过合理使用 @Transactional
注解和事务传播机制,可以确保在不同场景下的数据一致性和业务逻辑的完整性。
# 七、总结与最佳实践
- 事务控制边界: 建议在服务层(Service Layer)进行事务控制,而不是在 DAO 层或控制层。
- 异常处理: 在业务层中尽量不要捕获并处理异常,而是让异常冒泡到控制层进行统一处理,以确保事务能正确回滚。
- 传播机制选择: 根据业务需求选择合适的事务传播机制,例如在某些情况下使用
REQUIRES_NEW
来确保子事务的独立性。 - 性能优化: 对于只读的操作,可以通过设置
read-only = true
来优化性能。