Spring事务全攻略:从基础配置到高级避坑
在企业级Java开发中,Spring事务管理是保证数据一致性的核心能力,也是面试高频考点。但实际开发中,不少开发者会因对机制理解不深、配置不当,导致事务失效、数据错乱等问题。
本文整合了Spring事务的核心知识点,结合实战案例拆解声明式/编程式事务、传播行为、隔离级别,并针对原文中的细节错误进行勘正,补充高频避坑技巧与落地实践,无论你是初学者还是资深开发者,都能从中收获实用价值。
一、Spring事务管理概览
1.1 两种事务管理方式
声明式事务(主流选择)
通过注解或XML配置声明事务属性,Spring框架在运行时通过AOP自动管理事务边界,无需侵入业务代码,是日常开发的首选。
@Service
@Transactional
public class OrderService {
@Autowired
private OrderRepository orderRepository;
// 完整事务配置示例
@Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.READ_COMMITTED,
rollbackFor = Exception.class,
timeout = 30
)
public Order createOrder(OrderRequest request) {
Order order = convertToOrder(request);
return orderRepository.save(order);
}
}
优点
-
非侵入性,业务代码与事务代码解耦
-
配置简单,开发效率高
-
易于维护,事务配置集中管理
缺点
-
灵活性有限,事务边界固定为方法级别
-
存在自调用失效等陷阱(后文详细说明)
-
调试时堆栈信息复杂
编程式事务(特殊场景)
在代码中显式控制事务的开始、提交、回滚等操作,灵活性高,适合需要精细控制事务边界的场景。
@Service
public class OrderService {
@Autowired
private PlatformTransactionManager transactionManager;
@Autowired
private TransactionTemplate transactionTemplate;
// 方式1:使用TransactionTemplate简化编程式事务(推荐)
public Order createOrderProgrammatic(OrderRequest request) {
return transactionTemplate.execute(status -> {
Order order = convertToOrder(request);
// 可在此处精细控制事务逻辑
if (someCondition) {
status.setRollbackOnly(); // 标记回滚,下面修改数据库操作,回滚
}
return orderRepository.save(order);
});
}
// 方式2:使用PlatformTransactionManager(更底层控制)
public Order createOrderManual(OrderRequest request) {
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
TransactionStatus status = transactionManager.getTransaction(def);
try {
Order order = orderRepository.save(convertToOrder(request));
transactionManager.commit(status);
return order;
} catch (Exception e) {
transactionManager.rollback(status);
throw new BusinessException("创建订单失败", e);
}
}
}
适用场景
-
需要精细控制事务边界(如部分提交、条件回滚)
-
批量处理中的分批提交(避免大事务)
-
根据运行时条件动态决定事务行为
-
与复杂业务逻辑深度集成
1.2 选择建议
| 场景 | 推荐方式 | 理由 |
|---|---|---|
| 常规CRUD操作 | 声明式事务 | 简单高效,代码清晰 |
| 批量数据处理 | 编程式事务 | 可控制每批提交,避免大事务 |
| 微服务业务逻辑 | 声明式事务 | 符合微服务简洁性原则 |
| 复杂业务流程 | 混合使用 | 声明式为主,编程式为辅 |
| 性能敏感场景 | 编程式事务 | 可最小化事务范围 |
二、深入理解事务传播行为
事务传播行为定义了多个事务方法相互调用时,事务应该如何传播。这是Spring事务中最重要也最容易出错的概念之一,核心是理解7种传播行为的差异。
2.1 七种传播行为详解
@Service
public class PropagationDemoService {
@Transactional(propagation = Propagation.REQUIRED) // 默认值
public void outerMethod() {
// 业务逻辑...
innerMethod(); // 调用内部方法
}
@Transactional(propagation = Propagation.REQUIRED)
public void innerMethod() {
// 业务逻辑...
}
}
1. REQUIRED(默认)
-
行为:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务
-
适用场景:大多数业务方法(如订单创建、用户注册)
-
示例:
-
场景1:methodA无事务,methodB配置REQUIRED → methodB开启新事务
-
场景2:methodA有事务,methodB配置REQUIRED → methodB加入methodA的事务,任何一个方法抛出异常,整个事务回滚
2. REQUIRES_NEW
-
行为:无论当前是否存在事务,都创建一个新的事务
-
核心特性:新事务独立提交/回滚,不影响外层事务(关键区别于REQUIRED)
-
适用场景:日志记录、审计等不应受主业务影响的独立操作
@Service
public class OrderService {
@Transactional(propagation = Propagation.REQUIRED)
public void placeOrder(Order order) {
// 主业务逻辑
orderRepository.save(order);
// 记录操作日志 - 需要独立事务
try {
logService.logOrderOperation(order, "CREATED");
} catch (Exception e) {
// 日志失败不影响订单创建
log.error("记录日志失败", e);
}
}
}
@Service
public class LogService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void logOrderOperation(Order order, String action) {
// 这个操作在独立事务中执行
// 即使placeOrder()回滚,这个日志也会保留
auditLogRepository.save(new AuditLog(order, action));
}
}
3. NESTED
-
行为:如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则创建新事务
-
核心特性:嵌套事务是外层事务的子事务,提交依赖于外层事务;子事务回滚不影响外层事务(需数据库支持保存点,如MySQL InnoDB)
-
适用场景:可部分回滚的批量操作(如批量处理数据,单个数据失败不影响整体)
@Transactional
public void processBatch(List<Item> items) {
for (Item item : items) {
try {
processItem(item); // 嵌套事务
} catch (BusinessException e) {
// 当前item处理失败,继续处理下一个
log.error("处理item失败: {}", item.getId(), e);
}
}
}
@Transactional(propagation = Propagation.NESTED)
public void processItem(Item item) {
// 这个方法是嵌套事务
// 如果失败,只回滚这个item的操作
// 不影响外层事务和其他item
itemRepository.save(item);
}
4. SUPPORTS
-
行为:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行
-
适用场景:查询方法,可有可无事务(如订单详情查询)
@Transactional(propagation = Propagation.SUPPORTS)
public Order getOrderById(Long id) {
// 如果调用方有事务,则加入
// 如果调用方无事务,则无事务运行
return orderRepository.findById(id).orElse(null);
}
5. NOT_SUPPORTED
-
行为:以非事务方式执行,如果当前存在事务,则挂起当前事务
-
适用场景:不需要事务的查询,或与事务不兼容的操作(如复杂报表生成,避免长时间占用数据库连接)
6. NEVER
-
行为:以非事务方式执行,如果当前存在事务,则抛出异常
-
适用场景:强制要求不在事务中执行的方法(如发送消息、邮件等外部操作)
7. MANDATORY
-
行为:必须在一个已有的事务中执行,否则抛出异常
-
适用场景:必须被事务性方法调用的内部方法(如库存扣减,必须依赖订单事务)
2.2 传播行为选择指南(修正版)
| 传播行为 | 当前有事务 | 当前无事务 | 使用场景 |
|---|---|---|---|
| REQUIRED | 加入事务 | 创建事务 | 大多数业务方法(默认选择) |
| REQUIRES_NEW | 挂起当前,创建新事务 | 创建事务 | 独立操作(如日志、审计) |
| NESTED | 嵌套事务(保存点,需数据库支持) | 创建事务 | 可部分回滚的批量操作 |
| SUPPORTS | 加入事务 | 无事务运行 | 可有可无的查询 |
| NOT_SUPPORTED | 挂起事务 | 无事务运行 | 非事务性操作 |
| NEVER | 抛出异常 | 无事务运行 | 强制无事务 |
| MANDATORY | 加入事务 | 抛出异常 | 强制有事务 |
三、事务隔离级别深度解析(勘正版)
事务隔离级别定义了事务之间的可见性规则,用于解决并发场景下的脏读、不可重复读、幻读问题。不同数据库对隔离级别的支持存在差异,需重点注意MySQL的特殊实现。
3.1 四种隔离级别(修正关键细节)
1. READ_UNCOMMITTED(读未提交)
-
并发问题:可能发生脏读、不可重复读、幻读(安全性最低)
-
场景:对数据一致性要求极低,如某些临时统计场景
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public BigDecimal getTotalSales() {
// 允许读取未提交的数据
// 性能最高,但可能读到"脏数据"
return salesRepository.calculateTotal();
}
2. READ_COMMITTED(读已提交,多数数据库默认)
-
解决问题:脏读
-
遗留问题:不可重复读、幻读
-
场景:大多数业务场景(如转账、订单支付)
@Transactional(isolation = Isolation.READ_COMMITTED)
public void transfer(Long from, Long to, BigDecimal amount) {
// 只能读取已提交的数据
// 但同一个事务中两次读取同一数据可能结果不同
Account account = accountRepository.findById(from).orElseThrow();
// 其他事务可能在此修改了数据并提交
if (account.getBalance().compareTo(amount) < 0) {
throw new InsufficientBalanceException();
}
// ... 转账逻辑
}
3. REPEATABLE_READ(可重复读,MySQL默认)
勘正要点:原文提到“仍会出现幻读”,实际MySQL InnoDB存储引擎在该级别下,通过**间隙锁(Next-Key Locking)**机制已经解决了幻读问题(这是MySQL对SQL标准的增强)。
-
解决问题:脏读、不可重复读,MySQL InnoDB额外解决幻读
-
遗留问题:理论上仍可能存在幻读(SQL标准定义),但MySQL中已规避
-
场景:需要保证多次读取一致性的场景(如批量更新)
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void batchUpdateUserStatus(List<Long> userIds) {
// 第一次查询
List<User> users = userRepository.findByIdIn(userIds);
// 在此期间,其他事务无法修改这些用户的记录(间隙锁作用)
// 第二次查询,结果与第一次相同
users = userRepository.findByIdIn(userIds);
users.forEach(user -> {
user.setStatus(Status.ACTIVE);
userRepository.save(user);
});
}
4. SERIALIZABLE(序列化)
-
解决问题:脏读、不可重复读、幻读(安全性最高)
-
遗留问题:性能最差,可能大量锁等待,导致并发瓶颈
-
场景:对数据一致性要求极高,如金融核心交易(如股票下单、资金清算)
@Transactional(isolation = Isolation.SERIALIZABLE)
public void allocateSeat(BookingRequest request) {
// 完全串行化执行
// 确保不会发生超额预订
Long bookedCount = bookingRepository.countByFlightId(request.getFlightId());
Long totalSeats = flightRepository.getSeatsById(request.getFlightId());
if (bookedCount >= totalSeats) {
throw new NoSeatAvailableException();
}
bookingRepository.save(new Booking(request));
}
3.2 各数据库默认隔离级别补充
-
MySQL InnoDB:REPEATABLE_READ(默认,支持间隙锁)
-
Oracle/SQL Server:READ_COMMITTED(默认)
-
PostgreSQL:READ_COMMITTED(默认)
3.3 并发问题与隔离级别对应关系
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 性能 |
|---|---|---|---|---|
| READ_UNCOMMITTED | ✅ 存在 | ✅ 存在 | ✅ 存在 | 最高 |
| READ_COMMITTED | ❌ 解决 | ✅ 存在 | ✅ 存在 | 高 |
| REPEATABLE_READ(MySQL) | ❌ 解决 | ❌ 解决 | ❌ 解决(间隙锁) | 中 |
| SERIALIZABLE | ❌ 解决 | ❌ 解决 | ❌ 解决 | 低 |
四、Spring事务失效的12种场景与解决方案
事务失效是开发中最常见的问题,以下按“高频→低频”排序,结合勘正要点详细说明。
场景1:方法非public修饰
问题:Spring AOP代理要求被代理的方法必须是public的,非public(protected、private)方法的@Transactional注解会失效。
@Service
public class UserService {
// ❌ 错误:非public方法,事务不生效
@Transactional
protected void updateUserInternal(User user) {
userRepository.save(user);
}
// ✅ 正确:改为public
@Transactional
public void updateUser(User user) {
userRepository.save(user);
}
}
场景2:自调用问题
问题:同一个类中,一个非事务方法调用事务方法,事务不生效(Spring AOP仅代理外部调用,内部调用无法触发代理)。
@Service
public class OrderService {
public void createOrder(OrderRequest request) {
// ❌ 错误:自调用,事务失效
validateAndSave(request);
// ✅ 解决方案1:注入自身代理(推荐,无循环依赖风险)
OrderService selfProxy = SpringContextHolder.getBean(OrderService.class);
selfProxy.validateAndSave(request);
// ✅ 解决方案2:重构代码,将事务方法放到另一个Service
}
@Transactional
public void validateAndSave(OrderRequest request) {
validate(request);
orderRepository.save(convertToOrder(request));
}
}
勘正补充:原文使用applicationContext.getBean()直接获取代理,可能导致循环依赖;推荐使用Spring上下文工具类(如SpringContextHolder),或直接注入自身代理(@Autowired private OrderService selfProxy)。
场景3:异常类型错误
问题:默认情况下,Spring只在抛出RuntimeException和Error时才回滚事务,检查异常(如IOException)不会触发回滚。
@Service
public class UserService {
// ❌ 错误:抛出检查异常,默认不会回滚
@Transactional
public void createUser(User user) throws IOException {
userRepository.save(user);
if (someCondition) {
throw new IOException("文件写入失败"); // 检查异常
}
}
// ✅ 解决方案1:指定回滚异常类型
@Transactional(rollbackFor = Exception.class)
public void createUserFixed1(User user) throws IOException {
userRepository.save(user);
if (someCondition) {
throw new IOException("文件写入失败");
}
}
// ✅ 解决方案2:抛出运行时异常
@Transactional
public void createUserFixed2(User user) {
userRepository.save(user);
try {
// 可能抛出检查异常的操作
writeToFile(user);
} catch (IOException e) {
throw new RuntimeException("创建用户失败", e); // 包装为运行时异常
}
}
}
场景4:异常被捕获未重新抛出
问题:事务方法中捕获了异常,但没有重新抛出,Spring无法感知异常,不会触发回滚。
@Service
public class PaymentService {
@Transactional
public void processPayment(Payment payment) {
try {
paymentRepository.save(payment);
thirdPartyPaymentService.pay(payment);
} catch (Exception e) {
// ❌ 错误:捕获异常但没有重新抛出
log.error("支付处理失败", e);
// 事务不会回滚!
}
}
@Transactional
public void processPaymentFixed(Payment payment) {
try {
paymentRepository.save(payment);
thirdPartyPaymentService.pay(payment);
} catch (Exception e) {
log.error("支付处理失败", e);
// ✅ 正确:重新抛出异常
throw new RuntimeException("支付处理失败", e);
}
}
}
场景5:数据库引擎不支持事务
问题:使用不支持事务的存储引擎,如MySQL的MyISAM,即使配置了事务,也不会生效。
-- 检查表引擎
SHOW TABLE STATUS WHERE Name = 'your_table';
-- 修改为InnoDB引擎(支持事务)
ALTER TABLE your_table ENGINE = InnoDB;
场景6:传播行为配置错误
问题:错误配置了NOT_SUPPORTED、NEVER等传播行为,导致方法不在事务中运行。
@Service
public class AuditService {
// ❌ 错误:主业务需要事务,但配置了NOT_SUPPORTED
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void processWithAudit(BusinessData data) {
// 此方法不会在事务中运行
businessRepository.save(data);
auditRepository.saveAudit(data);
}
}
场景7:多数据源事务管理器配置错误
问题:多数据源环境下,没有正确指定事务管理器,导致事务无法关联到对应数据源。
@Configuration
public class DataSourceConfig {
@Bean(name = "primaryTransactionManager")
public PlatformTransactionManager primaryTransactionManager(
@Qualifier("primaryDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "secondaryTransactionManager")
public PlatformTransactionManager secondaryTransactionManager(
@Qualifier("secondaryDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
@Service
public class MultiDataSourceService {
// ❌ 错误:没有指定事务管理器
@Transactional
public void saveToPrimary(User user) {
// 默认使用primaryTransactionManager,若需操作从库则失效
userRepository.save(user);
}
// ✅ 正确:明确指定事务管理器
@Transactional(transactionManager = "secondaryTransactionManager")
public void saveToSecondary(Log log) {
logRepository.save(log);
}
}
场景8:@Transactional注解在接口上
问题:在接口方法上使用@Transactional,但使用了CGLIB代理(Spring默认代理方式),注解可能无法被识别。
勘正补充:从Spring 5.0开始,接口上的@Transactional注解会被正确识别,但最佳实践还是在实现类上使用(避免代理方式变更导致失效)。
// ❌ 不推荐在接口上使用
public interface UserService {
@Transactional
void saveUser(User user);
}
// ✅ 推荐在实现类上使用
@Service
public class UserServiceImpl implements UserService {
@Override
@Transactional
public void saveUser(User user) {
userRepository.save(user);
}
}
场景9:非受Spring管理的方法
问题:在非Spring管理的Bean(未加@Component、@Service等注解)上使用@Transactional,注解不生效。
// ❌ 错误:没有@Component、@Service等注解,非Spring管理
public class NotSpringBean {
@Autowired
private UserRepository userRepository;
@Transactional // 这个注解不会生效
public void save(User user) {
userRepository.save(user);
}
}
// ✅ 正确:添加Spring管理注解
@Component
public class SpringBean {
@Autowired
private UserRepository userRepository;
@Transactional // 这个注解会生效
public void save(User user) {
userRepository.save(user);
}
}
场景10:final或static方法(勘正描述)
问题:final或static方法不能被AOP代理,@Transactional注解失效。
勘正补充:static方法属于类而非实例,Spring AOP基于实例代理,无法代理static方法;且static方法无法访问Spring注入的实例变量(如Repository),即使不加@Transactional也无法正常工作。
@Service
public class UserService {
// ❌ 错误:final方法,事务不生效
@Transactional
public final void saveUserFinal(User user) {
userRepository.save(user);
}
// ❌ 错误:static方法,事务不生效
@Transactional
public static void saveUserStatic(User user) {
// 无法注入repository,因为static方法不能访问实例变量
}
}
场景11:事务方法内部开启新线程(勘正补充)
问题:事务上下文是线程绑定的,新线程中无法获取事务上下文,导致事务失效。
@Service
public class AsyncService {
@Transactional
public void processWithThread() {
userRepository.save(new User("main-thread"));
new Thread(() -> {
// ❌ 错误:新线程中无法获取事务
// 这个操作不在事务中
userRepository.save(new User("child-thread"));
}).start();
}
// ✅ 解决方案:使用@Async(需开启异步配置)
@Async
@Transactional(propagation = Propagation.REQUIRES_NEW)
public CompletableFuture<User> asyncSave(User user) {
User saved = userRepository.save(user);
return CompletableFuture.completedFuture(saved);
}
}
勘正补充:使用@Async+REQUIRES_NEW时,必须先开启异步配置(添加@EnableAsync注解),否则异步不生效,事务也无法正常工作:
// 必须添加@EnableAsync注解,开启异步支持
@Configuration
@EnableAsync
public class AsyncConfig {
// 配置线程池(推荐,避免默认线程池耗尽)
@Bean
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(20);
executor.setThreadNamePrefix("async-");
executor.initialize();
return executor;
}
}
场景12:try-catch位置错误
问题:try-catch包围了@Transactional注解的方法调用,异常被外层捕获,Spring无法感知异常,事务不回滚。
@Service
public class ProblemService {
public void problematicMethod() {
try {
// ❌ 错误:try-catch在代理方法调用之外
transactionalMethod();
} catch (Exception e) {
// 异常已经被代理处理,这里捕获不到
log.error("调用失败", e);
}
}
@Transactional
public void transactionalMethod() {
userRepository.save(new User());
throw new RuntimeException("测试异常");
}
}
补充:事务失效快速排查工具
添加通用排查方法,快速判断当前方法是否在事务中运行:
@Component
public class TransactionChecker {
/**
* 检查当前方法是否在事务中运行
*/
public void checkTransactionStatus() {
boolean isActive = TransactionSynchronizationManager.isActualTransactionActive();
String isolationLevel = getIsolationLevelName(TransactionSynchronizationManager.getCurrentTransactionIsolationLevel());
log.info("当前事务状态:{},隔离级别:{}",
isActive ? "活跃" : "未激活",
isolationLevel);
}
/**
* 转换隔离级别数字为可读名称
*/
private String getIsolationLevelName(Integer level) {
if (level == null) return "无";
return switch (level) {
case Connection.TRANSACTION_READ_UNCOMMITTED -> "READ_UNCOMMITTED";
case Connection.TRANSACTION_READ_COMMITTED -> "READ_COMMITTED";
case Connection.TRANSACTION_REPEATABLE_READ -> "REPEATABLE_READ";
case Connection.TRANSACTION_SERIALIZABLE -> "SERIALIZABLE";
default -> "未知(" + level + ")";
};
}
}
五、最佳实践总结
5.1 配置建议(事务超时配置)
勘正要点:原文spring.transaction.default-timeout是Spring Framework的底层配置,仅对编程式事务生效;声明式事务需通过@Transactional(timeout = 30)或数据源层面配置。
# application.yml(Spring Boot 2.x/3.x 正确配置)
spring:
datasource:
hikari:
transaction-timeout: 30000 # 毫秒级,全局声明式事务超时(推荐)
jpa:
show-sql: true # 显示SQL日志,便于调试
properties:
hibernate:
connection.isolation: 2 # READ_COMMITTED(对应数字2)
transaction:
timeout: 30 # 秒级,JPA层面事务超时
# 编程式事务超时配置(仅对TransactionTemplate生效)
transaction:
default-timeout: 30
5.2 编码规范
@Slf4j
@Service
@Transactional(
readOnly = true, // 类级别默认只读(查询方法复用)
timeout = 30, // 默认超时时间(秒)
rollbackFor = Exception.class // 默认所有异常都回滚(避免检查异常不回滚)
)
public class UserService {
@Autowired
private UserRepository userRepository;
// 查询方法 - 使用类级别默认配置(只读)
public Optional<User> findById(Long id) {
return userRepository.findById(id);
}
// 写方法 - 覆盖类级别配置(只读改为false)
@Transactional(
readOnly = false,
rollbackFor = BusinessException.class
)
public User createUser(UserCreateRequest request) {
validateRequest(request);
User user = convertToUser(request);
try {
return userRepository.save(user);
} catch (DataIntegrityViolationException e) {
log.error("创建用户失败", e);
throw new BusinessException("用户已存在");
}
}
// 批量操作 - 使用编程式事务(避免大事务)
@Autowired
private TransactionTemplate transactionTemplate;
public BatchResult batchImport(List<User> users) {
BatchResult result = new BatchResult();
// 分批处理,每批100条,缩小事务范围
for (List<User> batch : ListUtils.partition(users, 100)) {
try {
transactionTemplate.execute(status -> {
batch.forEach(userRepository::save);
return null;
});
result.addSuccess(batch.size());
} catch (Exception e) {
result.addFailure(batch.size());
log.error("批量导入失败", e);
}
}
return result;
}
// 工具方法:检查事务是否活跃
public boolean isTransactionActive() {
return TransactionSynchronizationManager.isActualTransactionActive();
}
}
5.3 补充:大事务优化实战(高频痛点)
大事务会导致数据库连接耗尽、锁等待超时等问题,核心优化思路是“最小化事务范围”:
@Service
public class BigTransactionOptimizer {
/**
* 优化前:大事务(查询+批量插入+远程调用)
*/
@Transactional
public void badBatchProcess(List<Data> dataList) {
// 1. 耗时查询(占用事务,无必要)
List<Config> configs = configRepository.findAll();
// 2. 批量插入(1000条,事务范围过大)
dataRepository.saveAll(dataList);
// 3. 远程调用(可能超时,占用事务)
remoteService.notify(dataList);
}
/**
* 优化后:最小化事务范围
*/
public void optimizedBatchProcess(List<Data> dataList) {
// 1. 非事务内执行耗时查询(脱离事务,减少锁占用)
List<Config> configs = configRepository.findAll();
// 2. 分批处理,缩小事务范围(每批100条)
for (List<Data> batch : ListUtils.partition(dataList, 100)) {
// 仅在批量插入时开启事务
transactionTemplate.execute(status -> {
dataRepository.saveAll(batch);
return null;
});
}
// 3. 事务外执行远程调用(避免远程调用超时导致事务长时间未提交)
remoteService.notify(dataList);
}
}
5.4 补充:事务测试示例(落地性提升)
通过单元测试验证事务行为,避免上线后出现事务失效问题:
@SpringBootTest
public class TransactionTest {
@Autowired
private OrderService orderService;
@Autowired
private OrderRepository orderRepository;
// 测试事务回滚是否生效
@Test
void testTransactionRollback() {
// 准备测试数据(非法金额,预期抛出异常)
OrderRequest request = new OrderRequest();
request.setAmount(-100);
// 执行方法(预期抛出异常)
assertThrows(BusinessException.class, () -> {
orderService.createOrder(request);
});
// 验证事务回滚:数据库中无新增订单
long count = orderRepository.count();
assertEquals(0, count);
}
// 测试传播行为REQUIRES_NEW是否生效
@Test
void testPropagationRequiresNew() {
// 执行主业务方法(包含日志记录,日志使用REQUIRES_NEW)
Order order = new Order();
order.setAmount(100);
orderService.placeOrder(order);
// 验证:即使主业务回滚,日志仍保留
long logCount = auditLogRepository.countByOrderId(order.getId());
assertEquals(1, logCount);
}
}
5.5 监控与排查
@Component
public class TransactionMonitor {
// 监控事务提交/回滚状态
@EventListener
public void handleTransactionEvent(TransactionApplicationEvent event) {
if (event instanceof TransactionCompletedEvent) {
TransactionCompletedEvent completed = (TransactionCompletedEvent) event;
log.info("事务完成: status={}, duration={}ms",
completed.getTransactionCompletionStatus(),
completed.getTransactionDuration().toMillis());
}
}
}
// 事务健康检查(集成Spring Actuator)
@Component
public class TransactionHealthIndicator implements HealthIndicator {
@Autowired
private PlatformTransactionManager transactionManager;
@Override
public Health health() {
try {
// 简单的事务健康检查:尝试获取事务
transactionManager.getTransaction(
new DefaultTransactionDefinition());
return Health.up().build();
} catch (Exception e) {
return Health.down(e).build();
}
}
}
配置Spring Actuator监控:
management:
endpoints:
web:
exposure:
include: metrics,health,transactions # 暴露事务监控端点
endpoint:
health:
show-details: always
六、总结
Spring事务管理的核心是“理解机制、正确配置、规避陷阱”。掌握以下关键点,就能构建出既健壮又高效的Spring数据访问层:
-
默认使用声明式事务,特殊场景(批量处理、精细控制)使用编程式事务;
-
重点理解事务传播行为,尤其是REQUIRED、REQUIRES_NEW、NESTED的区别,避免传播行为配置错误;
-
根据业务需求选择合适的隔离级别,MySQL用户需注意REPEATABLE_READ级别已解决幻读(间隙锁);
-
警惕事务失效的12种场景,尤其是自调用、异常处理、非public方法这3个高频问题;
-
优化大事务,通过“最小化事务范围、分批处理”提升系统并发能力;
-
添加事务监控与单元测试,提前发现并解决事务问题。
1451

被折叠的 条评论
为什么被折叠?



