声明式事务与编程式事务
候选人小蒋在面试蚂蚁 P7 时,面试官问道:
"你用过编程式事务吗?和声明式事务有什么区别?"
小蒋说:"用过 TransactionTemplate..."
面试官追问:"什么场景下你选择编程式事务而不是声明式?"
小蒋说:"呃...好像大部分时候都是声明式..."
面试官继续追问:"那你为什么要用编程式?"
小蒋答不上来。
【面试官心理】
这道题我用来测试候选人对事务的全面理解。只用声明式事务的人很多,但能说出编程式事务价值和适用场景的人很少。能说出"需要细粒度控制"、"需要在事务外执行"、"需要复用事务逻辑"等场景的,基本都有复杂业务经验。
一、核心问题 🔴
1.1 问题拆解
第一层:概念
- "什么是声明式事务?什么是编程式事务?"
- "两者在代码写法上有什么区别?"
第二层:对比
- "声明式事务和编程式事务各有什么优缺点?"
- "Spring 默认用的是哪种?"
第三层:场景选择
- "什么场景下应该用编程式事务?"
- "什么场景下用声明式事务就够了?"
第四层:实现细节
- "TransactionTemplate 是怎么工作的?"
- "PlatformTransactionManager 是什么?"
1.2 ❌ 错误示范
候选人原话 A:"声明式事务就是用 @Transactional,编程式事务就是用 TransactionTemplate。"
问题诊断:
候选人原话 B:"编程式事务性能更好,因为不需要 AOP 代理。"
问题诊断:
- 错误理解,编程式事务也有开销
- 两种方式的性能差异不是主要区别
候选人原话 C:"声明式事务更好,所以永远不用编程式。"
问题诊断:
1.3 标准回答
P5 回答:两种实现方式
声明式事务:通过 @Transactional 注解声明,由 Spring AOP 代理在方法前后自动管理事务。
编程式事务:通过 TransactionTemplate 或 PlatformTransactionManager 手动控制事务边界。
// 声明式事务(@Transactional)
@Service
public class OrderService {
@Transactional
public void createOrder(Order order) {
orderRepository.save(order);
paymentService.process(order.getPayment());
inventoryService.deduct(order.getItems());
}
}
// 编程式事务(TransactionTemplate)
@Service
public class OrderService {
@Autowired
private TransactionTemplate transactionTemplate;
public void createOrder(Order order) {
transactionTemplate.executeWithoutResult(status -> {
orderRepository.save(order);
paymentService.process(order.getPayment());
inventoryService.deduct(order.getItems());
});
}
}
// 编程式事务(PlatformTransactionManager)
@Service
public class OrderService {
@Autowired
private PlatformTransactionManager transactionManager;
public void createOrder(Order order) {
TransactionDefinition def = new DefaultTransactionDefinition();
TransactionStatus status = transactionManager.getTransaction(def);
try {
orderRepository.save(order);
paymentService.process(order.getPayment());
inventoryService.deduct(order.getItems());
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
throw e;
}
}
}
1.4 追问升级
追问 1:优缺点对比
追问 2:编程式事务的典型场景
场景一:需要在事务外执行某些操作
@Service
public class UserService {
@Autowired
private TransactionTemplate transactionTemplate;
public void registerUser(User user) {
// 1. 保存用户(需要事务)
User saved = transactionTemplate.execute(status -> {
return userRepository.save(user);
});
// 2. 发送欢迎邮件(不需要事务,且不应该在事务中)
// 因为如果事务回滚,邮件已经发出去了
emailService.sendWelcomeEmail(saved.getEmail());
}
}
⚠️
这里有个陷阱:如果 emailService.sendWelcomeEmail() 在事务外执行,而事务在后续逻辑中回滚了,用户没创建成功但邮件已经发出去了。正确做法是使用 TransactionSynchronization.afterCommit()。
public void registerUser(User user) {
User saved = userRepository.save(user); // 保存用户
// 注册事务提交后的回调
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronization() {
@Override
public void afterCommit() {
emailService.sendWelcomeEmail(saved.getEmail());
}
}
);
}
场景二:需要细粒度控制事务
@Service
public class DataImportService {
@Autowired
private TransactionTemplate transactionTemplate;
public void importBatch(List<Data> dataList) {
for (Data data : dataList) {
try {
transactionTemplate.executeWithoutResult(status -> {
importData(data);
});
} catch (Exception e) {
log.error("导入失败: " + data.getId(), e);
// 单条失败不影响其他条
}
}
}
}
场景三:需要复用事务逻辑
@Service
public class TransactionHelper {
@Autowired
private TransactionTemplate transactionTemplate;
// 封装事务逻辑
public <T> T executeInTransaction(Supplier<T> action) {
return transactionTemplate.execute(status -> {
return action.get();
});
}
public void executeInTransaction(Runnable action) {
transactionTemplate.executeWithoutResult(status -> action.run());
}
}
@Service
public class OrderService {
@Autowired
private TransactionHelper helper;
public void createOrder() {
helper.executeInTransaction(() -> {
orderRepository.save(order);
inventoryService.deduct();
});
}
}
追问 3:TransactionTemplate 的实现原理
// TransactionTemplate 继承自 DefaultTransactionDefinition
public class TransactionTemplate extends DefaultTransactionDefinition {
private final PlatformTransactionManager transactionManager;
@Override
public <T> T execute(TransactionCallback<T> action) {
// 1. 获取事务
TransactionStatus status = transactionManager.getTransaction(this);
try {
// 2. 执行业务逻辑
T result = action.doInTransaction(status);
// 3. 正常返回,提交事务
if (!status.isRollbackOnly()) {
transactionManager.commit(status);
}
return result;
} catch (Throwable ex) {
// 4. 异常,判断是否回滚
if (rollbackWhen(ex)) {
transactionManager.rollback(status);
} else {
transactionManager.commit(status);
}
throw ex;
}
}
private boolean rollbackWhen(Throwable ex) {
// 根据 rollbackOn 判断
return (ex instanceof RuntimeException || ex instanceof Error);
}
}
PlatformTransactionManager 接口:
public interface PlatformTransactionManager {
// 获取事务
TransactionStatus getTransaction(TransactionDefinition definition);
// 提交事务
void commit(TransactionStatus status);
// 回滚事务
void rollback(TransactionStatus status);
}
常见的实现类:
DataSourceTransactionManager:JDBC/MyBatis
JpaTransactionManager:JPA/Hibernate
HibernateTransactionManager:Hibernate
JtaTransactionManager:分布式事务(JTA)
二、延伸问题 🟡
2.1 Spring Boot 中的声明式事务
Spring Boot 自动配置了事务管理器,不需要手动配置:
// Spring Boot 自动配置
// DataSourceTransactionManagerAutoConfiguration
@Bean
@ConditionalOnMissingBean(PlatformTransactionManager.class)
public DataSourceTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
// JpaBaseConfiguration
@Bean
@ConditionalOnMissingBean(PlatformTransactionManager.class)
public JpaTransactionManager transactionManager() {
return new JpaTransactionManager();
}
多数据源时需要手动配置:
@Configuration
public class TransactionManagerConfig {
@Primary
@Bean
public DataSourceTransactionManager transactionManager1(DataSource dataSource1) {
return new DataSourceTransactionManager(dataSource1);
}
@Bean
public DataSourceTransactionManager transactionManager2(DataSource dataSource2) {
return new DataSourceTransactionManager(dataSource2);
}
}
// 使用时指定
@Service
public class MyService {
@Transactional("transactionManager2")
public void methodWithDs2() {
// 使用 dataSource2
}
}
2.2 声明式事务的局限
// 局限1:无法获取事务状态
@Service
public class OrderService {
@Transactional
public void createOrder() {
// ❌ 无法知道当前是否在事务中
// 无法动态决定是否开启新事务
}
}
// 编程式可以
public void createOrder() {
if (TransactionSynchronizationManager.isActualTransactionActive()) {
// 已经有事务
}
transactionTemplate.execute(status -> {
// 可以动态标记回滚
status.setRollbackOnly();
});
}
// 局限2:无法在事务外执行
@Service
public class OrderService {
@Transactional
public void createOrder() {
// ❌ 所有代码都在事务中
// 如果远程调用失败,整个事务都要回滚
paymentService.remoteCall(); // 耗时操作,不应该在事务中
}
}
// 编程式可以
public void createOrder() {
// 事务外执行远程调用
String result = paymentService.remoteCall(); // 不占事务
transactionTemplate.executeWithoutResult(status -> {
// 事务内执行本地操作
orderRepository.save(order);
});
}
三、生产避坑
3.1 混用两种事务的问题
@Service
public class BadService {
@Autowired
private TransactionTemplate transactionTemplate;
@Transactional // 声明式事务
public void methodA() {
// 这里是事务1
transactionTemplate.executeWithoutResult(status -> {
// ❌ 在声明式事务中又开启了一个编程式事务
// 编程式事务可能是新的事务(REQUIRES_NEW)
// 可能导致不一致
});
}
}
3.2 编程式事务的资源泄漏
@Service
public class BadService {
@Autowired
private PlatformTransactionManager transactionManager;
public void badMethod() {
TransactionStatus status = transactionManager.getTransaction(null);
try {
// 业务逻辑
} finally {
// ❌ 如果忘记调用 commit 或 rollback
// 会导致连接泄漏
// 或者导致事务超时
}
}
}
// ✅ 正确做法:使用 TransactionTemplate
@Service
public class GoodService {
@Autowired
private TransactionTemplate transactionTemplate;
public void goodMethod() {
transactionTemplate.executeWithoutResult(status -> {
// 业务逻辑
// 即使抛出异常,TransactionTemplate 也会处理
});
}
}
四、工程选型
4.1 选择指南
4.2 最佳实践
// 推荐:封装编程式事务为工具类
@Component
public class TransactionExecutor {
@Autowired
private TransactionTemplate transactionTemplate;
public <T> T execute(Supplier<T> action) {
return transactionTemplate.execute(status -> action.get());
}
public void executeVoid(Runnable action) {
transactionTemplate.executeWithoutResult(status -> action.run());
}
// 带超时控制
public <T> T execute(Supplier<T> action, int timeoutSeconds) {
transactionTemplate.setTimeout(timeoutSeconds);
return execute(action);
}
}
// 使用
@Service
public class OrderService {
@Autowired
private TransactionExecutor executor;
public void createOrder() {
executor.executeVoid(() -> {
orderRepository.save(order);
});
}
}
五、面试总结
声明式 vs 编程式事务,不是"谁更好"的问题,而是"谁更适合"的问题。
P5 候选人能说出两种写法的区别。
P6 候选人能说出各自的优缺点,能说出简单的场景选择。
P7 候选人能说出编程式事务的典型场景(事务外执行、细粒度控制),能说出混用的问题和最佳实践。
记住,大多数场景声明式事务就够了,但遇到复杂业务时,编程式事务是你的好帮手。