声明式事务:深度解析与实战指南
🏗️ 一、事务的底层原理(Spring实现)
1.1 核心实现机制
java
// Spring事务底层架构
┌─────────────────────────────────────────────────────────────┐
│ 业务代码 (@Transactional) │
├─────────────────────────────────────────────────────────────┤
│ ↗ │
│ 代理对象 (JDK Proxy / CGLIB) │
│ ↓ │
│ TransactionInterceptor (AOP切面) │
│ ├─ 获取事务属性 (@Transactional配置) │
│ ├─ 确定PlatformTransactionManager │
│ ├─ 处理传播行为 │
│ ├─ 开启/加入事务 │
│ ├─ 执行业务方法 │
│ ├─ 异常回滚/提交 │
│ └─ 清理资源 │
├─────────────────────────────────────────────────────────────┤
│ DataSourceTransactionManager (事务管理器) │
│ ├─ 获取数据库连接 │
│ ├─ 设置隔离级别/只读 │
│ ├─ 开启数据库事务 │
│ ├─ 提交/回滚 │
│ └─ 关闭连接 │
├─────────────────────────────────────────────────────────────┤
│ JDBC Driver / 数据库 │
│ ├─ REDO Log (重做日志) │
│ ├─ UNDO Log (回滚日志) │
│ ├─ Lock (锁机制) │
│ └─ MVCC (多版本并发控制) │
└─────────────────────────────────────────────────────────────┘
1.2 关键源码流程
java
// 简化的TransactionInterceptor流程
public class TransactionInterceptor extends TransactionAspectSupport {
public Object invoke(MethodInvocation invocation) throws Throwable {
// 1. 获取事务属性
TransactionAttribute txAttr = getTransactionAttributeSource()
.getTransactionAttribute(invocation.getMethod(),
invocation.getThis().getClass());
// 2. 确定事务管理器
PlatformTransactionManager tm = determineTransactionManager(txAttr);
// 3. 创建事务
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr,
invocation.getMethod().getDeclaringClass().getName() + "." +
invocation.getMethod().getName());
Object retVal;
try {
// 4. 执行业务方法
retVal = invocation.proceed();
} catch (Throwable ex) {
// 5. 异常回滚
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
} finally {
// 6. 清理事务信息
cleanupTransactionInfo(txInfo);
}
// 7. 提交事务
commitTransactionAfterReturning(txInfo);
return retVal;
}
}
// ThreadLocal事务同步管理
public abstract class TransactionSynchronizationManager {
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");
// 绑定当前线程的数据库连接
public static void bindResource(Object key, Object value) {
// 每个线程独立的事务上下文
}
}
🔄 二、传播行为的实际业务应用
2.1 七种传播行为实战场景
java
@Service
@Transactional
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private PaymentService paymentService;
@Autowired
private InventoryService inventoryService;
@Autowired
private LogService logService;
/**
* 场景1:REQUIRED (默认) - 主业务方法
* 特性:有事务加入,无事务新建
* 适用:80%的业务场景
*/
public Order createOrder(OrderRequest request) {
// 1. 创建订单(主事务开始)
Order order = orderRepository.save(convertToOrder(request));
// 2. 扣减库存(加入主事务)
inventoryService.deductStock(order.getItems());
// 3. 发起支付(加入主事务)
paymentService.processPayment(order);
return order;
}
/**
* 场景2:REQUIRES_NEW - 独立日志/审计
* 特性:新建独立事务,挂起当前事务
* 适用:日志、审计等辅助操作
*/
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void auditLog(String action, String content) {
// 即使主事务回滚,审计日志仍会保存
auditRepository.save(new AuditLog(action, content));
}
/**
* 场景3:NESTED - 批量操作中的单个失败处理
* 特性:嵌套事务(保存点),外部回滚影响嵌套,嵌套回滚不影响外部
* 适用:批量处理,部分失败不影响整体
*/
@Transactional
public BatchResult batchCreateOrders(List<OrderRequest> requests) {
BatchResult result = new BatchResult();
for (int i = 0; i < requests.size(); i++) {
try {
// 嵌套事务:单个失败回滚到保存点
createOrderWithNested(requests.get(i));
result.addSuccess(requests.get(i));
} catch (Exception e) {
// 仅这个订单失败,不影响其他
result.addFailure(requests.get(i), e.getMessage());
}
}
return result;
}
@Transactional(propagation = Propagation.NESTED)
public void createOrderWithNested(OrderRequest request) {
// 嵌套事务执行
if (isInvalid(request)) {
throw new ValidationException("订单无效");
}
createOrder(request);
}
/**
* 场景4:SUPPORTS - 查询操作
* 特性:有事务加入,没有则非事务执行
* 适用:查询服务,可读缓存
*/
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
public Order getOrder(Long orderId) {
// 如果调用方有事务,则加入;没有则以非事务执行
return orderRepository.findById(orderId).orElse(null);
}
/**
* 场景5:NOT_SUPPORTED - 非事务操作
* 特性:以非事务方式执行,挂起当前事务
* 适用:发送通知、消息队列等
*/
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void sendNotification(Order order) {
// 发送邮件/SMS,不需要事务支持
notificationService.sendOrderCreatedEmail(order);
}
/**
* 场景6:MANDATORY - 强制事务环境
* 特性:必须在事务中调用,否则抛异常
* 适用:核心业务方法,必须事务保护
*/
@Transactional(propagation = Propagation.MANDATORY)
public void updateOrderStatus(Long orderId, OrderStatus status) {
// 必须在外层事务中调用
// 防止单独调用导致数据不一致
orderRepository.updateStatus(orderId, status);
}
/**
* 场景7:NEVER - 禁止事务环境
* 特性:必须在非事务中调用,否则抛异常
* 适用:统计计算、报表生成等长时间操作
*/
@Transactional(propagation = Propagation.NEVER)
public Statistics calculateMonthlyStatistics() {
// 复杂的统计计算,不应该在事务中执行
return statisticsService.calculate();
}
}
2.2 业务场景选择指南
java
// 电商系统中的传播行为应用
public class EcommerceTransactionExample {
// ✅ 正确:下单流程(REQUIRED + REQUIRES_NEW)
@Transactional
public Order placeOrder(OrderRequest request) {
// 1. 创建订单(主事务)
Order order = orderService.createOrder(request);
try {
// 2. 扣库存(REQUIRED,加入主事务)
inventoryService.deductStock(request.getItems());
// 3. 扣款(REQUIRED,加入主事务)
paymentService.deductBalance(request.getUserId(),
request.getTotalAmount());
// 4. 发送确认短信(REQUIRES_NEW,独立事务)
// 即使下单失败,短信也要发(比如告知用户失败原因)
smsService.sendOrderConfirmSms(request.getPhone());
} catch (Exception e) {
// 5. 记录失败日志(REQUIRES_NEW,独立事务)
logService.logOrderFailure(request, e);
throw e;
}
return order;
}
// ❌ 错误:混用传播行为导致问题
@Transactional
public void wrongExample() {
// 主事务开始...
// ❌ 在同一个方法内混用REQUIRED和REQUIRES_NEW
// 可能导致锁等待、死锁或业务逻辑混乱
orderService.updateOrder(); // REQUIRED
// 这里如果REQUIRES_NEW失败,主事务继续,但业务可能不一致
auditService.auditOperation(); // REQUIRES_NEW
}
}
🚦 三、隔离级别的业务应用
3.1 四种隔离级别实战
java
@Service
public class IsolationLevelService {
/**
* 1. READ_UNCOMMITTED(读未提交)
* 问题:脏读、不可重复读、幻读
* 适用:统计系统(近似值即可),对数据准确性要求不高
*/
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public BigDecimal getApproximateTotalSales() {
// 获取近似销售总额(允许脏读)
// 适合大数据看板,不需要精确值
return salesRepository.sumAllSales();
}
/**
* 2. READ_COMMITTED(读已提交) - Oracle默认
* 问题:不可重复读、幻读
* 适用:大多数OLTP系统
*/
@Transactional(isolation = Isolation.READ_COMMITTED)
public void transferMoney(Long fromAccountId, Long toAccountId,
BigDecimal amount) {
// 转账:读取已提交的数据
Account from = accountRepository.findById(fromAccountId);
Account to = accountRepository.findById(toAccountId);
// 这里可能有不可重复读问题,但可以接受
if (from.getBalance().compareTo(amount) < 0) {
throw new InsufficientBalanceException();
}
from.setBalance(from.getBalance().subtract(amount));
to.setBalance(to.getBalance().add(amount));
}
/**
* 3. REPEATABLE_READ(可重复读) - MySQL默认
* 问题:幻读
* 适用:对数据一致性要求高的场景
*/
@Transactional(isolation = Isolation.REPEATABLE_READ)
public FinancialReport generateFinancialReport(LocalDate startDate,
LocalDate endDate) {
// 生成财务报表:需要保证读取期间数据不变
// 1. 读取期初余额
BigDecimal openingBalance = accountRepository
.getBalanceAtDate(startDate);
// 2. 读取期间交易(可重复读保证这些数据不变)
List<Transaction> transactions = transactionRepository
.findByDateBetween(startDate, endDate);
// 3. 计算期末余额
// 在REPEATABLE_READ下,其他事务不能修改这些历史记录
return new FinancialReport(openingBalance, transactions);
}
/**
* 4. SERIALIZABLE(串行化)
* 问题:性能低
* 适用:资金清算、对账等强一致性场景
*/
@Transactional(isolation = Isolation.SERIALIZABLE)
public SettlementResult dailySettlement() {
// 日终清算:必须完全串行化执行
// 1. 锁定所有相关账户
// 2. 计算利息
// 3. 更新余额
// 4. 生成结算记录
// 完全避免并发问题,但性能最差
return settlementService.execute();
}
/**
* 实际选择策略
*/
@Transactional(
isolation = Isolation.READ_COMMITTED, // 默认选择
readOnly = true, // 只读查询
timeout = 5 // 5秒超时
)
public List<Order> searchOrders(OrderQuery query) {
// 查询操作:读已提交 + 只读 + 超时控制
return orderRepository.search(query);
}
@Transactional(
isolation = Isolation.REPEATABLE_READ, // 高一致性
rollbackFor = BusinessException.class, // 明确回滚异常
timeout = 30 // 30秒超时
)
public void batchSettlement(List<Long> orderIds) {
// 批量结算:需要更高隔离级别
for (Long orderId : orderIds) {
settlementService.settleOrder(orderId);
}
}
}
3.2 隔离级别引发的经典问题
java
// 并发问题重现示例
public class ConcurrencyProblems {
// 1. 脏读(READ_UNCOMMITTED时发生)
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void dirtyReadDemo() throws InterruptedException {
// 事务A:开始转账但未提交
new Thread(() -> {
accountRepository.updateBalance(1L, new BigDecimal("900")); // 1000→900
// 此时未提交!
Thread.sleep(1000);
// 模拟异常,回滚
throw new RuntimeException("转账失败");
}).start();
Thread.sleep(500); // 等待事务A执行更新但未提交
// 事务B:读取到未提交的数据(脏读)
BigDecimal balance = accountRepository.getBalance(1L); // 读到900
System.out.println("脏读到的余额: " + balance); // 实际应该是1000
}
// 2. 不可重复读(READ_COMMITTED时发生)
@Transactional(isolation = Isolation.READ_COMMITTED)
public void nonRepeatableReadDemo() throws InterruptedException {
// 事务A:第一次读取
BigDecimal firstRead = accountRepository.getBalance(1L); // 1000
// 事务B:修改数据并提交
new Thread(() -> {
accountRepository.updateBalance(1L, new BigDecimal("1500"));
}).start();
Thread.sleep(1000);
// 事务A:第二次读取,值变了!
BigDecimal secondRead = accountRepository.getBalance(1L); // 1500
System.out.println("不可重复读: " + firstRead + " → " + secondRead);
}
// 3. 幻读(REPEATABLE_READ时发生)
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void phantomReadDemo() {
// 事务A:查询年龄>20的用户数量
long firstCount = userRepository.countByAgeGreaterThan(20); // 10人
// 事务B:插入一个新用户(年龄25)
new Thread(() -> {
userRepository.save(new User("新用户", 25));
}).start();
// 事务A:再次查询,数量变了!
long secondCount = userRepository.countByAgeGreaterThan(20); // 11人
System.out.println("幻读: " + firstCount + " → " + secondCount);
}
}
⚠️ 四、@Transactional注解失效的十大陷阱
4.1 常见失效场景及解决方案
java
@Service
public class TransactionTrapService {
// ============ 陷阱1:自调用问题 ============
public void trap1SelfInvocation() {
// ❌ 问题:同一个类中方法调用,不走代理
saveUserAndLog(); // @Transactional不会生效!
// ✅ 解决方案1:注入自己(通过代理调用)
@Autowired
private TransactionTrapService self;
public void solution1() {
self.saveUserAndLog(); // 通过代理调用
}
// ✅ 解决方案2:拆分类
@Service
class UserService {
@Transactional
public void saveUserAndLog() { ... }
}
}
@Transactional
public void saveUserAndLog() {
userRepository.save(new User());
logRepository.save(new Log());
}
// ============ 陷阱2:异常类型不匹配 ============
public void trap2ExceptionType() {
// ❌ 默认只回滚RuntimeException和Error
@Transactional
public void method1() throws Exception {
throw new Exception(); // 不会回滚!
}
// ✅ 明确指定回滚异常
@Transactional(rollbackFor = Exception.class)
public void method2() throws Exception {
throw new Exception(); // 会回滚
}
// ✅ 指定不回滚的异常
@Transactional(noRollbackFor = BusinessException.class)
public void method3() {
throw new BusinessException(); // 业务异常,不回滚
}
}
// ============ 陷阱3:异常被捕获 ============
public void trap3ExceptionCaught() {
// ❌ 异常被捕获,不会触发回滚
@Transactional
public void method1() {
try {
userRepository.save(user);
throw new RuntimeException();
} catch (Exception e) {
// 异常被吞了!
log.error("保存失败", e);
}
}
// ✅ 重新抛出异常
@Transactional
public void method2() {
try {
userRepository.save(user);
throw new RuntimeException();
} catch (Exception e) {
log.error("保存失败", e);
throw e; // 重新抛出
}
}
// ✅ 抛出RuntimeException
@Transactional
public void method3() {
try {
userRepository.save(user);
throw new Exception();
} catch (Exception e) {
log.error("保存失败", e);
throw new RuntimeException(e); // 包装为RuntimeException
}
}
}
// ============ 陷阱4:非public方法 ============
public void trap4NonPublicMethod() {
// ❌ 非public方法,@Transactional无效
@Transactional
private void privateMethod() { ... }
@Transactional
protected void protectedMethod() { ... }
@Transactional
void packagePrivateMethod() { ... }
// ✅ 必须是public方法
@Transactional
public void publicMethod() { ... }
}
// ============ 陷阱5:数据库引擎不支持 ============
public void trap5DatabaseEngine() {
// ❌ MyISAM引擎不支持事务
// ✅ 使用InnoDB引擎
// 检查方法
public void checkTableEngine() {
// SHOW TABLE STATUS LIKE 'table_name';
// Engine字段应为InnoDB
}
}
// ============ 陷阱6:多数据源未指定 ============
public void trap6MultipleDataSources() {
// ❌ 多数据源时未指定transactionManager
@Transactional // 使用默认数据源
// ✅ 明确指定事务管理器
@Transactional("orderTransactionManager")
public void saveOrder() { ... }
@Transactional("userTransactionManager")
public void saveUser() { ... }
}
// ============ 陷阱7:异步方法 ============
public void trap7AsyncMethod() {
// ❌ @Async + @Transactional 可能有问题
@Async
@Transactional
public void asyncMethod() {
// 事务可能在新线程中不生效
}
// ✅ 解决方案:在异步方法内部处理事务
@Async
public void asyncMethod() {
// 手动管理事务
transactionTemplate.execute(status -> {
// 业务逻辑
return null;
});
}
}
// ============ 陷阱8:propagation设置不当 ============
public void trap8WrongPropagation() {
// ❌ 在需要事务的方法中使用NOT_SUPPORTED
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void criticalOperation() {
// 关键操作,却不在事务中!
}
// ✅ 根据业务选择正确的传播行为
@Transactional(propagation = Propagation.REQUIRED)
public void criticalOperation() { ... }
}
// ============ 陷阱9:final/static方法 ============
public void trap9FinalStaticMethod() {
// ❌ final方法无法被代理
@Transactional
public final void finalMethod() { ... }
// ❌ static方法无法被代理
@Transactional
public static void staticMethod() { ... }
}
// ============ 陷阱10:父子容器问题 ============
public void trap10ParentChildContainer() {
// ❌ Spring MVC中,如果@Service在子容器,@Transactional在父容器
// 可能导致代理不生效
// ✅ 确保配置正确
// 1. 启用注解驱动:@EnableTransactionManagement
// 2. 扫描包路径正确
// 3. 事务管理器Bean正确配置
}
}
4.2 事务调试与监控
java
@Component
@Slf4j
public class TransactionDebugger {
// 1. 启用事务调试日志
@Configuration
public static class LogConfig {
// application.yml
// logging:
// level:
// org.springframework.transaction: TRACE
// org.springframework.jdbc: DEBUG
// org.springframework.orm.jpa: DEBUG
}
// 2. 事务监控切面
@Aspect
@Component
public static class TransactionMonitorAspect {
@Around("@annotation(transactional)")
public Object monitorTransaction(ProceedingJoinPoint pjp,
Transactional transactional) throws Throwable {
String methodName = pjp.getSignature().toShortString();
long startTime = System.currentTimeMillis();
log.info("事务开始: {} [propagation={}, isolation={}]",
methodName,
transactional.propagation(),
transactional.isolation());
try {
Object result = pjp.proceed();
long duration = System.currentTimeMillis() - startTime;
log.info("事务提交: {} [耗时={}ms]", methodName, duration);
// 长时间事务告警
if (duration > 5000) {
log.warn("长时间事务警告: {} 耗时 {}ms", methodName, duration);
}
return result;
} catch (Exception e) {
log.error("事务回滚: {} [异常={}]", methodName, e.getClass().getSimpleName());
throw e;
}
}
}
// 3. 检查事务是否生效
public void checkTransactionActive() {
boolean hasTransaction = TransactionSynchronizationManager
.isActualTransactionActive();
String transactionName = TransactionSynchronizationManager
.getCurrentTransactionName();
log.info("当前事务状态: active={}, name={}",
hasTransaction, transactionName);
// 打印事务详细信息
if (hasTransaction) {
Map<Object, Object> resources = TransactionSynchronizationManager
.getResourceMap();
log.debug("事务资源: {}", resources);
}
}
// 4. 事务健康检查
@Component
public static class TransactionHealthCheck {
@Scheduled(fixedDelay = 60000) // 每分钟检查一次
public void checkLongRunningTransactions() {
// 查询长时间运行的事务(需要数据库支持)
// MySQL: SELECT * FROM information_schema.INNODB_TRX
// WHERE TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) > 60
log.info("检查长时间运行的事务...");
}
@EventListener
public void handleTransactionEvent(ApplicationEvent event) {
if (event instanceof TransactionCompletionEvent) {
TransactionCompletionEvent txEvent = (TransactionCompletionEvent) event;
log.info("事务完成: {}, 耗时: {}ms",
txEvent.getTransactionName(),
txEvent.getDuration().toMillis());
}
}
}
}
🎯 五、面试题标准答案
Q1:Spring声明式事务的实现原理?
答 :
Spring通过AOP(动态代理)+ ThreadLocal实现声明式事务:
- 创建代理:为
@Transactional标注的Bean创建JDK或CGLIB代理 - 事务拦截:
TransactionInterceptor拦截方法调用 - 事务管理:
PlatformTransactionManager管理事务生命周期 - 资源绑定:通过
TransactionSynchronizationManager的ThreadLocal绑定连接资源 - 异常处理:根据异常类型决定回滚或提交
Q2:传播行为REQUIRED和REQUIRES_NEW的区别?
答:
- REQUIRED:有事务则加入,没有则新建。适用大多数业务方法
- REQUIRES_NEW:总是新建事务,挂起当前事务。适用日志、审计等独立操作
业务场景:
- 下单流程用REQUIRED(订单、库存、支付在同一个事务)
- 记录操作日志用REQUIRES_NEW(即使下单失败,日志也要记录)
Q3:@Transactional在哪些情况下会失效?
答:
- 自调用:同一个类中方法调用不走代理
- 异常被捕获:异常没抛出,事务不知道要回滚
- 异常类型不匹配:默认只回滚RuntimeException
- 非public方法:代理只能拦截public方法
- 数据库引擎不支持:如MyISAM
- 多数据源未指定:需明确transactionManager
- 传播行为设置错误:如用了NOT_SUPPORTED
Q4:如何选择隔离级别?
答:
- READ_COMMITTED:大多数场景,平衡性能与一致性
- REPEATABLE_READ:对一致性要求高,如财务报表
- SERIALIZABLE:强一致性场景,如资金清算
- READ_UNCOMMITTED:统计系统,允许脏读
Q5:事务超时如何设置?为什么重要?
答:
java
@Transactional(timeout = 30) // 30秒超时
重要性:
- 防止长事务阻塞数据库连接
- 避免死锁长时间占用资源
- 提高系统响应性
- 自动回滚超时操作
Q6:编程式事务 vs 声明式事务?
答:
- 声明式事务 :
@Transactional注解,代码侵入小,推荐使用 - 编程式事务 :手动
TransactionTemplate,控制精细,复杂场景使用
Q7:如何调试事务问题?
答:
- 开启DEBUG日志:
logging.level.org.springframework.transaction=DEBUG - 检查事务是否激活:
TransactionSynchronizationManager.isActualTransactionActive() - 监控事务执行时间
- 使用事务事件监听器
📚 六、实战检查清单
java
// 事务使用最佳实践检查清单
public class TransactionChecklist {
public void beforeCommitCode() {
// ✅ 检查点1:是否使用了正确的传播行为?
// ✅ 检查点2:是否设置了合适的隔离级别?
// ✅ 检查点3:是否指定了rollbackFor?
// ✅ 检查点4:是否设置了超时时间?
// ✅ 检查点5:只读查询是否标记了readOnly=true?
// ✅ 检查点6:是否避免了在事务中进行远程调用?
// ✅ 检查点7:是否避免了在事务中进行文件IO?
// ✅ 检查点8:事务方法是否保持简短?
// ✅ 检查点9:是否处理了自调用问题?
// ✅ 检查点10:多数据源是否指定了transactionManager?
}
// 事务配置验证
@Test
public void testTransactionConfiguration() {
// 1. 验证事务管理器Bean存在
assertNotNull(applicationContext.getBean(PlatformTransactionManager.class));
// 2. 验证@EnableTransactionManagement启用
// 3. 验证数据源配置正确
// 4. 验证扫描包路径包含@Service
}
}
掌握这些内容,你就能在事务相关的面试和实际开发中游刃有余。关键是要理解原理、知道如何选择配置、避免常见陷阱。
java
package com.deploy.platform.other.transactional;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.concurrent.atomic.AtomicReference;
/**
* 账户服务类 - 演示事务隔离级别
*/
@Service
class LocalAccountService {
// 使用AtomicReference确保线程安全
private final AtomicReference<Account> accountHolder =
new AtomicReference<>(new Account(1L, new BigDecimal("1000")));
/**
* 模拟脏读场景 - READ_UNCOMMITTED隔离级别
*/
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void simulateDirtyRead() throws InterruptedException {
final Account originalAccount = accountHolder.get();
System.out.println("事务A开始 - 当前余额: " + originalAccount.getBalance());
// 模拟事务A:更新余额但未提交
Thread transactionA = new Thread(() -> {
Account currentAccount = accountHolder.get();
Account updatedAccount = new Account(
currentAccount.getId(),
currentAccount.getBalance().subtract(new BigDecimal("100"))
);
System.out.println("事务A:更新余额为 " + updatedAccount.getBalance() + " (未提交)");
accountHolder.set(updatedAccount);
try {
// 模拟处理时间
Thread.sleep(2000);
// 模拟异常回滚
System.out.println("事务A:发生异常,回滚到原始余额");
accountHolder.set(originalAccount);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
accountHolder.set(originalAccount); // 中断时回滚
}
});
// 模拟事务B:读取未提交的数据
Thread transactionB = new Thread(() -> {
try {
// 等待事务A执行更新操作
Thread.sleep(500);
Account dirtyReadAccount = accountHolder.get();
System.out.println("事务B:读取到余额 " + dirtyReadAccount.getBalance() +
" (可能为脏读)");
// 再次等待,查看最终结果
Thread.sleep(3000);
Account finalAccount = accountHolder.get();
System.out.println("事务B:最终余额为 " + finalAccount.getBalance());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
transactionA.start();
transactionB.start();
transactionA.join();
transactionB.join();
}
/**
* 获取当前账户余额
*/
public BigDecimal getCurrentBalance() {
return accountHolder.get().getBalance();
}
}
/**
* 账户实体类
*/
@Data
@AllArgsConstructor
class Account {
private Long id;
private BigDecimal balance;
}
/**
* 测试类
*/
public class TransactionTestDemo1215 {
public static void main(String[] args) throws InterruptedException {
// 在实际Spring环境中,应该通过依赖注入获取AccountService
LocalAccountService accountService = new LocalAccountService();
System.out.println("=== 开始演示脏读 ===");
System.out.println("初始余额: " + accountService.getCurrentBalance());
accountService.simulateDirtyRead();
System.out.println("=== 演示结束 ===");
System.out.println("最终余额: " + accountService.getCurrentBalance());
}
}