本文介绍 Spring 提供的事务管理 API,以及编程式事务、XML 声明式事务和注解声明式事务三种配置方式。
一、为什么需要 Spring 事务 API?
前面我们通过 AOP + 自定义事务管理器的方式控制事务,虽然解决了代码侵入问题,但仍需要自己编写事务管理的代码(开启、提交、回滚等),较为繁琐。
Spring 提供了一套完整的事务管理 API,我们只需要配置,无需手写事务控制逻辑。
二、Spring 事务管理的三大核心 API
2.1 PlatformTransactionManager(平台事务管理器)
这是 Spring 事务管理的核心接口,负责真正的事务管理工作。常用实现类如下:
| 实现类 | 底层技术 |
|---|---|
DataSourceTransactionManager |
JDBC / MyBatis |
HibernateTransactionManager |
Hibernate |
2.2 TransactionDefinition(事务定义信息)
用于定义事务的相关属性:
事务隔离级别
| 常量 | 说明 |
|---|---|
ISOLATION_DEFAULT |
使用数据库默认隔离级别(Spring 默认值) |
ISOLATION_READ_UNCOMMITTED |
读未提交,可能出现脏读 |
ISOLATION_READ_COMMITTED |
读已提交,解决脏读(Oracle 默认) |
ISOLATION_REPEATABLE_READ |
可重复读,解决不可重复读(MySQL 默认) |
ISOLATION_SERIALIZABLE |
串行化,解决幻读,性能最低 |
事务传播行为
传播行为决定了当一个事务方法被另一个事务方法调用时,如何处理事务:
| 传播行为 | 说明 |
|---|---|
REQUIRED |
有事务则加入,没有则新建(默认值,适用于增删改) |
SUPPORTS |
有事务则加入,没有则以非事务方式执行(适用于查询) |
MANDATORY |
必须在事务中运行,否则抛出异常 |
REQUIRES_NEW |
新建事务,如果当前有事务则挂起 |
NOT_SUPPORTED |
以非事务方式运行,如果有事务则挂起 |
NEVER |
以非事务方式运行,如果有事务则抛出异常 |
NESTED |
在嵌套事务中运行(有事务则嵌套,没有则类似 REQUIRED) |
其他属性
- 超时时间 (
timeout):事务最长执行时间,默认-1(无限制),单位为秒 - 是否只读 (
readOnly):只读事务用于查询操作,可提升性能
2.3 TransactionStatus(事务状态)
描述某个时间点上事务的状态,提供刷新、回滚、判断等方法(通常由框架内部使用)。
三、编程式事务(了解)
Spring 编程式事务控制是不常用的方式,这里简单了解即可。
3.1 配置平台事务管理器和事务模板
xml
<!-- 配置平台事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务管理模板 -->
<bean id="transactionTemplate"
class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"></property>
</bean>
3.2 在业务类中注入并使用
xml
<bean id="accountService" class="com.xq.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
<property name="transactionTemplate" ref="transactionTemplate"></property>
</bean>
在业务方法中,使用 TransactionTemplate 的 execute() 方法执行事务逻辑,代码侵入感依然较强,实际项目不推荐。
四、声明式事务 ------ XML 方式(重点)
声明式事务通过配置的方式管理事务,业务代码完全不需要修改。这是实际项目中最常用的方式之一。
4.1 去掉事务管理模板,回归纯净业务类
业务类只负责业务逻辑,不含任何事务代码:
java
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public void transfer(String sourceName, String targetName, Double money) {
Account sourceAccount = accountDao.findAccountByName(sourceName);
Account targetAccount = accountDao.findAccountByName(targetName);
sourceAccount.setMoney(sourceAccount.getMoney() - money);
targetAccount.setMoney(targetAccount.getMoney() + money);
accountDao.updateAccount(sourceAccount);
int i = 1 / 0; // 模拟异常
accountDao.updateAccount(targetAccount);
}
}
4.2 配置平台事务管理器
xml
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
4.3 配置事务增强(tx:advice)
xml
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- name:给哪个业务方法配置事务 -->
<!-- propagation:事务传播行为,REQUIRED 是默认值 -->
<tx:method name="transfer" propagation="REQUIRED"/>
<!-- 也可以用通配符,为所有方法配置 -->
<!-- <tx:method name="*" propagation="REQUIRED"/> -->
<!-- 查询方法设为只读 -->
<!-- <tx:method name="find*" propagation="SUPPORTS" read-only="true"/> -->
</tx:attributes>
</tx:advice>
4.4 配置 AOP,将事务增强和切点关联
xml
<aop:config>
<aop:pointcut
expression="execution(* com.xq.service.impl.*.*(..))"
id="p1">
</aop:pointcut>
<!-- 使用 advisor 将事务增强绑定到切点 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="p1"></aop:advisor>
</aop:config>
4.5 完整配置文件
xml
<bean id="accountDao" class="com.xq.dao.impl.AccountDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<bean id="accountService" class="com.xq.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/ssm"></property>
<property name="user" value="root"></property>
<property name="password" value="Admin123!"></property>
</bean>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="transfer" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut expression="execution(* com.xq.service.impl.*.*(..))" id="p1"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="p1"></aop:advisor>
</aop:config>
五、声明式事务 ------ 注解方式(重点)
注解方式是目前最流行的事务配置方式,步骤更简单。
5.1 配置开启注解对事务的支持
xml
<!-- 配置平台事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 开启注解事务支持 -->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
5.2 在业务类(或方法)上添加 @Transactional 注解
java
@Service
@Transactional // 作用在类上,对所有方法生效
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
// 也可以在方法上单独配置
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT)
public void transfer(String sourceName, String targetName, Double money) {
Account sourceAccount = accountDao.findAccountByName(sourceName);
Account targetAccount = accountDao.findAccountByName(targetName);
sourceAccount.setMoney(sourceAccount.getMoney() - money);
targetAccount.setMoney(targetAccount.getMoney() + money);
accountDao.updateAccount(sourceAccount);
int i = 1 / 0;
accountDao.updateAccount(targetAccount);
}
}
@Transactional 注解支持的属性:
| 属性 | 说明 | 默认值 |
|---|---|---|
propagation |
传播行为 | REQUIRED |
isolation |
隔离级别 | DEFAULT |
timeout |
超时时间(秒) | -1 |
readOnly |
是否只读 | false |
rollbackFor |
指定回滚的异常类型 | 运行时异常 |
noRollbackFor |
指定不回滚的异常类型 | --- |
六、三种事务方式对比
| 方式 | 复杂度 | 代码侵入 | 推荐程度 |
|---|---|---|---|
| 编程式事务 | 低 | 高(业务代码含事务逻辑) | 不推荐 |
| XML 声明式事务 | 中 | 无 | 推荐(配置集中) |
| 注解声明式事务 | 低 | 极低(只加注解) | 推荐(代码简洁) |
七、小结
Spring 的事务管理体现了框架设计的精髓:
- 编程式事务:灵活但繁琐,了解即可
- XML 声明式事务:配置统一,适合大型项目统一管理
- 注解声明式事务:简洁直观,是目前主流的选择
理解事务的传播行为和隔离级别,是面试和实际开发中的重点,需要重点掌握。