Skip to content

编程式事务管理

编程式事务管理是通过编写代码来显式控制事务的开始、提交和回滚。Spring提供了两种编程式事务管理的方式:使用TransactionTemplate和直接使用PlatformTransactionManager

准备工作

在开始之前,需要在项目中添加必要的依赖:

xml
<dependencies>
    <!-- Spring Core & Context -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.20</version>
    </dependency>
    
    <!-- Spring JDBC -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.3.20</version>
    </dependency>
    
    <!-- Database Connection Pool -->
    <dependency>
        <groupId>com.zaxxer</groupId>
        <artifactId>HikariCP</artifactId>
        <version>4.0.3</version>
    </dependency>
    
    <!-- H2 Database (for example) -->
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <version>2.1.214</version>
    </dependency>
</dependencies>

配置事务管理器

首先,需要配置数据源和事务管理器:

java
@Configuration
public class TransactionConfig {
    
    @Bean
    public DataSource dataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:h2:mem:testdb");
        config.setUsername("sa");
        config.setPassword("");
        config.setDriverClassName("org.h2.Driver");
        return new HikariDataSource(config);
    }
    
    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dataSource());
    }
    
    @Bean
    public TransactionTemplate transactionTemplate() {
        return new TransactionTemplate(transactionManager());
    }
}

使用TransactionTemplate

TransactionTemplate是Spring提供的基于模板方法模式的事务管理工具,它处理了事务的开始、提交和异常时的回滚,使用起来比较简洁。

基本用法

java
@Service
public class AccountServiceImpl implements AccountService {
    
    private final JdbcTemplate jdbcTemplate;
    private final TransactionTemplate transactionTemplate;
    
    public AccountServiceImpl(JdbcTemplate jdbcTemplate, TransactionTemplate transactionTemplate) {
        this.jdbcTemplate = jdbcTemplate;
        this.transactionTemplate = transactionTemplate;
    }
    
    @Override
    public void transfer(String fromAccount, String toAccount, double amount) {
        transactionTemplate.execute(status -> {
            try {
                // 检查余额
                double balance = getBalance(fromAccount);
                if (balance < amount) {
                    throw new InsufficientFundsException("余额不足");
                }
                
                // 转出
                jdbcTemplate.update(
                    "UPDATE accounts SET balance = balance - ? WHERE account_id = ?",
                    amount, fromAccount
                );
                
                // 模拟随机故障
                if (Math.random() < 0.1) {
                    throw new RuntimeException("随机故障");
                }
                
                // 转入
                jdbcTemplate.update(
                    "UPDATE accounts SET balance = balance + ? WHERE account_id = ?",
                    amount, toAccount
                );
                
                return null; // 成功完成事务
            } catch (InsufficientFundsException e) {
                // 业务异常,标记事务回滚
                status.setRollbackOnly();
                throw e;
            }
        });
    }
    
    private double getBalance(String accountId) {
        return jdbcTemplate.queryForObject(
            "SELECT balance FROM accounts WHERE account_id = ?",
            Double.class, accountId
        );
    }
}

自定义事务属性

TransactionTemplate允许自定义事务属性,如隔离级别、超时时间等:

java
@Service
public class ProductServiceImpl implements ProductService {
    
    private final JdbcTemplate jdbcTemplate;
    private final TransactionTemplate transactionTemplate;
    
    public ProductServiceImpl(JdbcTemplate jdbcTemplate, TransactionTemplate transactionTemplate) {
        this.jdbcTemplate = jdbcTemplate;
        
        // 自定义事务属性
        this.transactionTemplate = new TransactionTemplate(transactionTemplate.getTransactionManager());
        this.transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
        this.transactionTemplate.setTimeout(30); // 30秒超时
        this.transactionTemplate.setReadOnly(true); // 只读事务
    }
    
    @Override
    public List<Product> findAllProducts() {
        return transactionTemplate.execute(status -> {
            return jdbcTemplate.query(
                "SELECT * FROM products",
                (rs, rowNum) -> new Product(
                    rs.getLong("id"),
                    rs.getString("name"),
                    rs.getDouble("price")
                )
            );
        });
    }
}

直接使用PlatformTransactionManager

直接使用PlatformTransactionManager提供了更底层的控制,但需要手动处理事务的开始、提交和回滚。

java
@Service
public class OrderServiceImpl implements OrderService {
    
    private final JdbcTemplate jdbcTemplate;
    private final PlatformTransactionManager transactionManager;
    
    public OrderServiceImpl(JdbcTemplate jdbcTemplate, PlatformTransactionManager transactionManager) {
        this.jdbcTemplate = jdbcTemplate;
        this.transactionManager = transactionManager;
    }
    
    @Override
    public void createOrder(Order order) {
        // 定义事务属性
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        def.setIsolationLevel(TransactionDefinition.ISOLATION_REPEATABLE_READ);
        def.setTimeout(30); // 30秒超时
        
        // 开始事务
        TransactionStatus status = transactionManager.getTransaction(def);
        
        try {
            // 保存订单
            jdbcTemplate.update(
                "INSERT INTO orders (id, customer_id, amount, created_at) VALUES (?, ?, ?, ?)",
                order.getId(), order.getCustomerId(), order.getAmount(), new Date()
            );
            
            // 保存订单项
            for (OrderItem item : order.getItems()) {
                jdbcTemplate.update(
                    "INSERT INTO order_items (order_id, product_id, quantity, price) VALUES (?, ?, ?, ?)",
                    order.getId(), item.getProductId(), item.getQuantity(), item.getPrice()
                );
                
                // 更新库存
                int updated = jdbcTemplate.update(
                    "UPDATE inventory SET stock = stock - ? WHERE product_id = ? AND stock >= ?",
                    item.getQuantity(), item.getProductId(), item.getQuantity()
                );
                
                if (updated == 0) {
                    throw new StockShortageException("产品 " + item.getProductId() + " 库存不足");
                }
            }
            
            // 提交事务
            transactionManager.commit(status);
        } catch (Exception e) {
            // 回滚事务
            transactionManager.rollback(status);
            throw e;
        }
    }
}

保存点管理

在某些场景下,可能需要在事务中设置保存点,以便在发生某些异常时回滚到特定的保存点而不是整个事务:

java
@Service
public class BatchProcessServiceImpl implements BatchProcessService {
    
    private final JdbcTemplate jdbcTemplate;
    private final PlatformTransactionManager transactionManager;
    
    public BatchProcessServiceImpl(JdbcTemplate jdbcTemplate, PlatformTransactionManager transactionManager) {
        this.jdbcTemplate = jdbcTemplate;
        this.transactionManager = transactionManager;
    }
    
    @Override
    public void processBatch(List<Item> items) {
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        TransactionStatus status = transactionManager.getTransaction(def);
        
        try {
            for (int i = 0; i < items.size(); i++) {
                // 每处理10个项目设置一个保存点
                if (i % 10 == 0) {
                    Object savepoint = status.createSavepoint();
                    
                    try {
                        processItems(items.subList(i, Math.min(i + 10, items.size())));
                    } catch (Exception e) {
                        // 发生异常时回滚到保存点,继续处理其他批次
                        status.rollbackToSavepoint(savepoint);
                        logFailedBatch(items.subList(i, Math.min(i + 10, items.size())), e);
                    }
                }
            }
            
            // 提交整个事务
            transactionManager.commit(status);
        } catch (Exception e) {
            // 回滚整个事务
            transactionManager.rollback(status);
            throw e;
        }
    }
    
    private void processItems(List<Item> items) {
        for (Item item : items) {
            jdbcTemplate.update(
                "INSERT INTO processed_items (id, data, processed_at) VALUES (?, ?, ?)",
                item.getId(), item.getData(), new Date()
            );
        }
    }
    
    private void logFailedBatch(List<Item> items, Exception e) {
        // 记录失败的批次到日志系统
    }
}

编程式事务管理的优缺点

优点

  1. 细粒度控制: 可以精确控制事务的边界,适用于复杂的事务场景。
  2. 灵活性: 可以动态决定是否需要事务,以及何时开始和提交事务。
  3. 保存点支持: 可以在事务内设置保存点,在需要时回滚到特定点。

缺点

  1. 代码侵入: 事务管理代码与业务逻辑混合,降低了代码的可读性。
  2. 重复代码: 在多个需要事务的方法中,需要编写类似的事务管理代码。
  3. 维护成本高: 如果需要修改事务属性,需要修改业务代码。

最佳实践

  1. 优先使用TransactionTemplate: 相比直接使用PlatformTransactionManager,TransactionTemplate代码更简洁,异常处理更简单。
  2. 合理设置事务属性: 根据业务需求设置合适的事务传播行为和隔离级别。
  3. 考虑声明式事务: 对于简单的事务需求,考虑使用更简洁的声明式事务管理。
  4. 异常处理: 明确区分业务异常和技术异常,决定哪些异常应该导致事务回滚。
  5. 避免长事务: 避免在一个事务中执行耗时的操作,特别是外部系统调用。

总结

编程式事务管理提供了对事务控制的最大灵活性,适用于需要细粒度控制事务边界的复杂场景。在选择事务管理方式时,应根据具体场景需求权衡编程式和声明式事务管理的优缺点。对于大多数简单的事务需求,声明式事务管理是更推荐的方式;而对于需要复杂控制流程的场景,编程式事务管理则更为适合。