声明式事务:深度解析与实战指南

声明式事务:深度解析与实战指南

🏗️ 一、事务的底层原理(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实现声明式事务:

  1. 创建代理:为@Transactional标注的Bean创建JDK或CGLIB代理
  2. 事务拦截:TransactionInterceptor拦截方法调用
  3. 事务管理:PlatformTransactionManager管理事务生命周期
  4. 资源绑定:通过TransactionSynchronizationManager的ThreadLocal绑定连接资源
  5. 异常处理:根据异常类型决定回滚或提交

Q2:传播行为REQUIRED和REQUIRES_NEW的区别?

  • REQUIRED:有事务则加入,没有则新建。适用大多数业务方法
  • REQUIRES_NEW:总是新建事务,挂起当前事务。适用日志、审计等独立操作

业务场景

  • 下单流程用REQUIRED(订单、库存、支付在同一个事务)
  • 记录操作日志用REQUIRES_NEW(即使下单失败,日志也要记录)

Q3:@Transactional在哪些情况下会失效?

  1. 自调用:同一个类中方法调用不走代理
  2. 异常被捕获:异常没抛出,事务不知道要回滚
  3. 异常类型不匹配:默认只回滚RuntimeException
  4. 非public方法:代理只能拦截public方法
  5. 数据库引擎不支持:如MyISAM
  6. 多数据源未指定:需明确transactionManager
  7. 传播行为设置错误:如用了NOT_SUPPORTED

Q4:如何选择隔离级别?

  • READ_COMMITTED:大多数场景,平衡性能与一致性
  • REPEATABLE_READ:对一致性要求高,如财务报表
  • SERIALIZABLE:强一致性场景,如资金清算
  • READ_UNCOMMITTED:统计系统,允许脏读

Q5:事务超时如何设置?为什么重要?

java 复制代码
@Transactional(timeout = 30) // 30秒超时

重要性

  1. 防止长事务阻塞数据库连接
  2. 避免死锁长时间占用资源
  3. 提高系统响应性
  4. 自动回滚超时操作

Q6:编程式事务 vs 声明式事务?

  • 声明式事务@Transactional注解,代码侵入小,推荐使用
  • 编程式事务 :手动TransactionTemplate,控制精细,复杂场景使用

Q7:如何调试事务问题?

  1. 开启DEBUG日志:logging.level.org.springframework.transaction=DEBUG
  2. 检查事务是否激活:TransactionSynchronizationManager.isActualTransactionActive()
  3. 监控事务执行时间
  4. 使用事务事件监听器

📚 六、实战检查清单

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());
    }
}
相关推荐
laocooon5238578865 小时前
C#二次开发中简单块的定义与应用
android·数据库·c#
不穿格子的程序员5 小时前
Redis篇4——Redis深度剖析:内存淘汰策略与缓存的三大“天坑”
数据库·redis·缓存·雪崩·内存淘汰策略
hans汉斯6 小时前
【软件工程与应用】平移置换搬迁系统设计与实现
数据库·人工智能·系统架构·软件工程·汉斯出版社·软件工程与应用
gugugu.6 小时前
Redis List类型完全指南:从原理到实战应用
数据库·redis·list
Hello.Reader6 小时前
Flink SQL ALTER 语句在线演进 Table/View/Function/Catalog/Model
数据库·sql·flink
白学还是没白学?6 小时前
exec db docker from A to B
数据库·docker·容器
云老大TG:@yunlaoda3606 小时前
腾讯云国际站代理商TCCC的技术适配服务包括哪些内容?
数据库·云计算·腾讯云
元气满满-樱6 小时前
MySql部署多实例
数据库·mysql·adb