七、Spring 事务控制 – 核心重点
在上述转账测试中,如果在扣钱和加钱之间抛出异常(如int a = 6/0;),会出现扣钱成功、加钱失败的情况,违反数据库事务的一致性。Spring 提供了完善的事务控制方案,基于 AOP 实现无侵入式的事务管理,解决该问题。
7.1 事务的核心概念
7.1.1 数据库事务四大特性(ACID)
7.1.2 事务并发的三大问题
| 脏读 | 一个事务读取到另一个事务未提交的数据 |
| 不可重复读 | 一个事务内多次读取同一数据,结果不一致(被其他事务修改并提交) |
| 幻读 | 一个事务内多次读取同一表,结果条数不一致(被其他事务插入 / 删除并提交) |
7.2 Spring 事务控制核心 API
Spring 事务控制基于 AOP 实现,核心有 3 个接口,均在org.springframework.transaction包下:
7.2.1 PlatformTransactionManager(事务管理器)
核心作用:提供事务的开启、提交、回滚核心方法,是 Spring 事务控制的核心接口,针对不同的持久层框架有不同的实现类:
| DataSourceTransactionManager | 适用于 Spring JDBC/MyBatis/MyBatis-Spring |
| HibernateTransactionManager | 适用于 Hibernate 框架 |
| JpaTransactionManager | 适用于 JPA 框架 |
public interface PlatformTransactionManager {
// 开启事务,返回事务状态
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
// 提交事务
void commit(TransactionStatus status) throws TransactionException;
// 回滚事务
void rollback(TransactionStatus status) throws TransactionException;
}
7.2.2 TransactionDefinition(事务定义)
核心作用:定义事务的属性,如隔离级别、传播行为、超时时间、读写性等,核心属性如下:
(1)事务隔离级别
解决事务并发的三大问题,Spring 提供 5 种隔离级别(默认使用数据库默认隔离级别,MySQL 为 REPEATABLE_READ,Oracle 为 READ_COMMITTED):
| ISOLATION_DEFAULT | 默认值,使用数据库自身的隔离级别 |
| ISOLATION_READ_UNCOMMITTED | 最低级别,允许脏读、不可重复读、幻读 |
| ISOLATION_READ_COMMITTED | 避免脏读,允许不可重复读、幻读 |
| ISOLATION_REPEATABLE_READ | 避免脏读、不可重复读,允许幻读 |
| ISOLATION_SERIALIZABLE | 最高级别,避免所有并发问题,事务串行执行 |
(2)事务传播行为
核心作用:定义一个事务方法被另一个事务方法调用时,事务的执行规则(如是否新建事务、是否加入原有事务),Spring 提供 7 种传播行为,PROPAGATION_REQUIRED为最常用:
| PROPAGATION_REQUIRED | 若当前无事务则新建,若有则加入(默认) |
| PROPAGATION_SUPPORTS | 支持当前事务,无事务则以非事务执行 |
| PROPAGATION_MANDATORY | 必须在事务中执行,无事务则抛异常 |
| PROPAGATION_REQUIRES_NEW | 强制新建事务,若有当前事务则挂起 |
| PROPAGATION_NOT_SUPPORTED | 以非事务执行,若有当前事务则挂起 |
| PROPAGATION_NEVER | 以非事务执行,若有当前事务则抛异常 |
| PROPAGATION_NESTED | 嵌套事务,若当前有事务则在嵌套事务中执行 |
(3)其他属性
- 超时时间:事务执行的最长时间,超时则自动回滚,默认-1(永不超时);
- 读写性:readOnly=true为只读事务(适用于查询,提高效率),readOnly=false为读写事务(适用于增删改,默认)。
7.2.3 TransactionStatus(事务状态)
核心作用:表示当前事务的状态信息,如是否是新事务、是否需要回滚等。
7.3 基于 XML 的 Spring 事务控制(AOP 实现)
Spring 事务控制基于 AOP 实现,核心是配置事务增强、切入点、切面,让事务逻辑织入到业务方法中。
7.3.1 完善 Spring 配置文件
在applicationContext.xml中添加事务管理器、事务增强、AOP 切面配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!– 原有配置:加载配置文件、数据源、SqlSessionFactory、Mapper扫描、注解扫描 –>
<context:property-placeholder location="classpath:db.properties" />
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClass}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="typeAliasesPackage" value="com.hg.pojo"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.hg.mapper"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
<context:component-scan base-package="com.hg"></context:component-scan>
<!– 新增事务配置 –>
<!– 1. 配置事务管理器(MyBatis使用DataSourceTransactionManager) –>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!– 2. 配置事务增强(tx:advice),指定事务属性 –>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!– 匹配业务方法,配置事务属性 –>
<!– 所有方法:读写事务,传播行为REQUIRED –>
<tx:method name="*" read-only="false" propagation="REQUIRED"/>
<!– 以select/get开头的方法:只读事务,提高查询效率 –>
<tx:method name="select*" read-only="true" propagation="SUPPORTS"/>
<tx:method name="get*" read-only="true" propagation="SUPPORTS"/>
</tx:attributes>
</tx:advice>
<!– 3. 配置AOP切面,将事务增强织入到切入点 –>
<aop:config>
<!– 切入点:匹配Service层所有方法 –>
<aop:pointcut id="txPointcut" expression="execution(* com.hg.service.*.*(..))"/>
<!– 切面:事务增强 + 切入点 –>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
</beans>
7.3.2 测试事务回滚
修改UserServiceImpl的转账方法,手动抛出异常,测试事务是否回滚:
@Override
public void updateUser(String source, String target, Float money) {
userMapper.updateUserOfSub(source, money);
// 手动抛出算术异常,模拟业务出错
int a = 6 / 0;
userMapper.updateUserOfAdd(target, money);
}
重新执行testUpdate测试方法,控制台抛出异常,数据库中张三丰的余额未减少,宋远桥的余额未增加,说明事务回滚成功,解决了数据一致性问题!
7.4 基于注解的 Spring 事务控制
注解配置比 XML 配置更简洁,开发中更常用,核心注解为@Transactional,可标注在类或方法上。
7.4.1 开启注解事务支持
在applicationContext.xml中删除 XML 的事务增强和 AOP 配置,仅保留事务管理器和注解事务开启:
<!– 事务管理器(必须保留) –>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!– 开启Spring注解事务支持 –>
<tx:annotation-driven transaction-manager="transactionManager"/>
7.4.2 使用 @Transactional 注解
@Transactional可标注在类上(所有方法生效)或方法上(仅当前方法生效,方法注解优先级高于类注解),通过注解属性配置事务属性。
package com.hg.service.impl;
import com.hg.mapper.UserMapper;
import com.hg.pojo.User;
import com.hg.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
// 类上标注:所有方法默认使用该事务属性
@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
// 方法上标注:可单独配置(优先级更高)
@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public void updateUser(String source, String target, Float money) {
userMapper.updateUserOfSub(source, money);
int a = 6 / 0; // 手动抛异常
userMapper.updateUserOfAdd(target, money);
}
@Override
// 查询方法:只读事务
@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
public void addUser(User user) {
userMapper.addUser(user);
}
}
@Transactional核心属性与TransactionDefinition一致,常用属性:
- readOnly:是否只读,默认false;
- propagation:传播行为,默认Propagation.REQUIRED;
- isolation:隔离级别,默认Isolation.DEFAULT;
- timeout:超时时间,默认-1;
- rollbackFor:指定触发回滚的异常类型,默认运行时异常回滚,检查异常不回滚。
重新执行测试方法,事务依然能成功回滚,注解配置生效!
八、Spring 核心知识点总结
8.1 核心思想
8.2 配置方式对比
| XML 配置 | 配置与代码分离,便于统一管理 | 配置文件繁琐,开发效率低 | 大型项目、需要统一管理配置 |
| 注解配置 | 简洁高效,开发效率高 | 配置分散在代码中,不易维护 | 中小型项目、快速开发 |
| 纯注解配置(Spring Boot) | 无 XML,极致简洁 | 需熟悉注解规则 | Spring Boot/Spring Cloud 项目 |
8.3 开发最佳实践
8.4 后续学习方向
掌握 Spring 核心(IOC+AOP + 事务)后,后续可深入学习:
九、常见问题与解决方案
问题:@Autowired注入时报NoSuchBeanDefinitionException?解决方案:检查是否开启注解扫描(context:component-scan)、类是否添加了@Component/@Service等注解、Bean 的类型是否匹配。
问题:Spring 整合 MyBatis 时,Mapper 注入时报错?解决方案:检查MapperScannerConfigurer的basePackage是否正确、Mapper 接口与 XML 的namespace是否一致、XML 文件是否被 Maven 扫描(需配置 build/resources)。
问题:事务不回滚?解决方案:
- 检查是否开启了注解事务支持(tx:annotation-driven);
- 检查事务管理器是否注入了数据源;
- 检查异常类型是否为运行时异常(默认仅运行时异常回滚,可通过rollbackFor指定检查异常);
- 检查是否手动捕获了异常(若捕获未抛出,事务无法感知,需手动throw new RuntimeException())。
问题:AOP 切面不生效?解决方案:检查是否开启 AOP 注解支持(aop:aspectj-autoproxy)、切面类是否添加@Aspect注解、切入点表达式是否正确匹配业务方法。
本文完,从 Spring 概述到 IOC、AOP,再到整合 MyBatis 和事务控制,全面讲解了 Spring 的核心知识点,结合实战案例让理论落地。掌握这些内容,就能轻松应对日常开发中的 Spring 使用场景,也为后续学习 Spring Boot/Spring Cloud 打下坚实基础!


