一、事务基本概念
事务是数据库系统中的重要概念,它是由一组操作所组成的逻辑单位,这组操作被视为一个整体,要么全部执行成功,要么全部回滚。事务的隔离级别可以控制多个事务之间的相互影响,而事务的传播行为可以控制当前事务对其他事务的可见性。
二、Spring 中的事务管理
Spring 提供了两种事务管理方式:编程式事务管理和声明式事务管理。编程式事务管理需要开发人员自己编写事务管理代码。而声明式事务管理则由 Spring 容器负责管理,需要开发人员在配置文件或者代码中指定事务管理规则。
声明式事务管理又分为两种实现方式:基于 TransactionProxyFactoryBean 的声明式事务管理和基于 @Transactional 注解的声明式事务管理。前者需要手动创建代理对象,后者则使用注解方式简化了配置。
编程式事务管理 | 声明式事务管理 | |
优点 | 可以更加灵活地控制事务 | 代码简洁,易于维护 |
可以适应复杂的业务场景 | 使用 AOP 技术,与业务逻辑解耦 | |
可以在代码中针对不同的异常进行回滚或提交 | 配置方便,可以通过配置文件轻松地进行切换和修改 | |
缺点 | 代码量较大 | 对于高度定制化的业务场景,可能会出现限制 |
不易维护 | 对于复杂的事务处理,可能需要多个声明式事务进行组合 | |
建议使用场景 | 应用需要局部控制事务,而非全局控制 | 应用需要全局控制事务 |
处理的业务比较复杂,需要编写更多的自定义逻辑 | 应用的事务需求相对简单 |
日常开发中我们多使用 @Transactional注解式事务管理,但在一些需要精细控制事务管理时往往力不从心,而被忽略的 编程式事务 就是解决此问题的利器。
三、使用 TransactionTemplate 进行编程式事务管理
TransactionTemplate是Spring框架提供的一个事务管理工具类,通过它我们可以手动控制事务的开始、提交和回滚等操作,方便我们更加灵活地处理事务,其主要提供了以下功能:
- 在事务内执行一组操作
- 设置事务的传播行为和隔离级别
- 设置事务的超时时间
- 优化只读事务
下面是一个简单的例子,演示了如何使用 TransactionTemplate 来实现编程式事务管理:
@Service
public class UserServiceImpl implements UserService {
private final UserDao userDao;
private final TransactionTemplate transactionTemplate;
@Autowired
public UserServiceImpl(UserDao userDao, TransactionTemplate transactionTemplate) {
this.userDao = userDao;
this.transactionTemplate = transactionTemplate;
}
@Override
public void transferMoney(Long fromUserId, Long toUserId, Double amount) {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
// 减少转出用户的余额
userDao.decreaseBalance(fromUserId, amount);
// 增加转入用户的余额
userDao.increaseBalance(toUserId, amount);
} catch (RuntimeException e) {
// 出现异常时回滚事务
status.setRollbackOnly();
throw e;
}
}
});
}
}
其它典型使用场景
1.在一个事务中执行多个操作
@Autowired
private TransactionTemplate transactionTemplate;
public void doMultipleOperations() {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
// 执行第一个操作
someDao.insert(foo);
// 执行第二个操作
otherDao.update(bar);
// 如果没有任何异常,则自动提交事务
}
});
}
2.跨多个数据源执行分布式事务
@Autowired
@Qualifier("db1TransactionManager")
private PlatformTransactionManager db1TransactionManager;
@Autowired
@Qualifier("db2TransactionManager")
private PlatformTransactionManager db2TransactionManager;
public void doDistributedTransaction() {
// 定义一个包含两个事务管理器的TransactionTemplate
TransactionTemplate template = new TransactionTemplate();
template.setTransactionManagers(db1TransactionManager, db2TransactionManager);
// 在TransactionTemplate中执行跨多个数据源的操作
template.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
someDao.insert(foo); // 在db1中执行操作
otherDao.update(bar); // 在db2中执行操作
}
});
}
3.处理同步/异步事务
@Service
public class UserServiceImpl implements UserService {
private final TransactionTemplate transactionTemplate;
private final ExecutorService executorService = Executors.newFixedThreadPool(10);
@Autowired
public UserServiceImpl(DataSourceTransactionManager transactionManager) {
this.transactionTemplate = new TransactionTemplate(transactionManager);
}
@Override
public void updateUser(User user) {
// 同步事务
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
// 更新数据库操作
userDao.updateUser(user);
} catch (Exception e) {
status.setRollbackOnly();
throw e;
}
}
});
// 异步事务
executorService.submit(() -> {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
// 向外部系统发送消息
messageService.sendMessage(user.getName() + "更新了资料");
} catch (Exception e) {
status.setRollbackOnly();
throw e;
}
}
});
});
}
}
在上面的例子中,我们使用了线程池来实现异步操作。在执行异步操作之前,我们先使用TransactionTemplate开启了一个同步事务,保证数据库操作与消息发送操作在同一事务中执行。接着,我们通过ExecutorService.submit()方法将消息发送操作提交到线程池中异步执行,同时在异步操作中再次使用TransactionTemplate开启了一个独立的事务。这样,我们就利用TransactionTemplate的传播行为机制,通过独立的事务管理异步操作,从而避免了多线程带来的线程安全问题,同时保证了数据的一致性和完整性。