Appearance
编程式事务管理
编程式事务管理是通过编写代码来显式控制事务的开始、提交和回滚。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) {
// 记录失败的批次到日志系统
}
}
编程式事务管理的优缺点
优点
- 细粒度控制: 可以精确控制事务的边界,适用于复杂的事务场景。
- 灵活性: 可以动态决定是否需要事务,以及何时开始和提交事务。
- 保存点支持: 可以在事务内设置保存点,在需要时回滚到特定点。
缺点
- 代码侵入: 事务管理代码与业务逻辑混合,降低了代码的可读性。
- 重复代码: 在多个需要事务的方法中,需要编写类似的事务管理代码。
- 维护成本高: 如果需要修改事务属性,需要修改业务代码。
最佳实践
- 优先使用TransactionTemplate: 相比直接使用PlatformTransactionManager,TransactionTemplate代码更简洁,异常处理更简单。
- 合理设置事务属性: 根据业务需求设置合适的事务传播行为和隔离级别。
- 考虑声明式事务: 对于简单的事务需求,考虑使用更简洁的声明式事务管理。
- 异常处理: 明确区分业务异常和技术异常,决定哪些异常应该导致事务回滚。
- 避免长事务: 避免在一个事务中执行耗时的操作,特别是外部系统调用。
总结
编程式事务管理提供了对事务控制的最大灵活性,适用于需要细粒度控制事务边界的复杂场景。在选择事务管理方式时,应根据具体场景需求权衡编程式和声明式事务管理的优缺点。对于大多数简单的事务需求,声明式事务管理是更推荐的方式;而对于需要复杂控制流程的场景,编程式事务管理则更为适合。