【Spring 事务】
Spring 事务理论
Spring 事务模块归属
Spring的事务管理属于Spring框架的Data Access/Integration核心模块,具体由Spring-TX模块(spring-tx
)实现。以下是关键点总结:
-
模块归属:
-
数据访问/集成模块(Data Access/Integration):负责数据库交互、事务管理、ORM集成等。
-
其中的Spring-TX模块(
spring-tx
)专门提供事务管理支持。
-
-
功能实现:
-
支持声明式事务(通过
@Transactional
注解或XML配置)和编程式事务(如TransactionTemplate
)。 -
依赖于AOP模块(切面编程)实现事务的拦截与增强,但其核心功能由
spring-tx
提供。
-
-
应用场景:
-
与JDBC、Hibernate、JPA等数据访问技术协同工作,确保事务的一致性。
-
总结:Spring事务的核心实现在spring-tx
模块中,归属于Spring的Data Access/Integration核心模块。
Spring 管理事务的方式
- 编程式事务管理:允许用户在实现代码中使用显式的方式调用beginTransaction()开启事务、commit()提交事务、rolback()回滚事务,从而可以达到精确定义事务的边界;
- 声明式事务管理:(实际是通过 AOP 实现,基于@Transactional 的全注解方式使用最多)。最大优点就是不需要编程,将事务管理从复杂业务逻辑中抽离,只需要在配置文件中配置并在目标方法上添加@Transactional注解即可实现。
推荐使用声明式事务管理,因为代码侵入性最小。
Spring 事务传播行为
事务传播行为
所谓事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播;是为了解决业务层方法之间互相调用的事务问题。Spring支持以下7种事务传播行为:
- 1.propagation-required: 支持当前事务,如果当前方法有事务,就加入,没有,就新建一个事务;
- 2.propagation-supports: 支持当前事务,如果当前方法有事务,就加入,没有,就以非事务的方式执行;
- 3.propagation-mandatory: 支持当前事务,如果当前方法有事务,就加入,没有,就抛出异常;
- 4.propagation-requires_new: 新建事务,如果当前存在事务,就把当前事务挂起;如果当前方法没有事务,就新建事务;
- 5.propagation-not-supported: 以非事务方式执行,如果当前方法存在事务就挂起当前事务;如果当前方法不存在事务,就以非事务方式执行;
- 6.propagation-never: 以非事务方式执行,如果当前方法存在事务就抛出异常;如果当前方法不存在事务,就以非事务方式执行;
- 7.propagation-nested: 如果当前方法有事务,则在嵌套事务内执行;如果当前方法没有事务,则与required操作类似;
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
Spring 的@Transactional注解
注解原理:
@Transactional 注解的工作机制是基于 AOP 实现的,AOP 又是使用动态代理实现的。如果目标对象实现了接口,默认情况下会采用 JDK 的动态代理,如果目标对象没有实现了接口,会使用 CGLIB 动态代理。
注解参数:
@Transactional
(propagation = Propagation.REQUIRES_NEW,
isolation = Isolation.DEFAULT,
timeout = -1,
readOnly = false)
常用配置参数总结(只列出了 5 个我平时比较常用的):
属性名 | 说明 |
propagation | 事务传播行为,默认REQUIRED ,作用是为了解决业务层方法之间互相调用的事务问题。 |
isolation | 事务隔离级别,默认DEFAULT (-1) |
timeout | 事务超时时间,默认-1。线程已经跑到方法里面,如果已经过去60秒(设置时间)了还没跑完这个方法,并且线程在这个方法中的后面还有涉及到对数据库的增删改查操作时会报事务超时错误(会回滚)。如果已经过去60秒了还没跑完但是后面已经没有涉及到对数据库的增删改查操作,那么这时不会报事务超时错误(不会回滚)。 |
readOnly | 返回是否为只读事务,默认值为 false。并不是不能在事务中进行修改等DML操作,它只是一个“暗示”,提示数据库驱动程序和数据库系统,这个事务并不包含更改数据的操作,那么JDBC驱动程序和数据库就有可能根据这种情况对该事务进行一些特定的优化,比方说不安排相应的数据库锁,以减轻事务对数据库的压力,毕竟事务也是要消耗数据库的资源的。 |
rollbackFor | 用于指定能够触发事务回滚的异常类型,并且可以指定多个异常类型。 |
注解使用:
注解作用于类、方法上,声明方法开启正常提交事务,异常回滚事务。
真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的。
注意:
- @Transactional注解只能在抛出RuntimeException或者Error时才会触发事务的回滚,常见的非RuntimeException是不会触发事务的回滚的。但是我们平时做业务处理时,需要捕获异常,所以可以手动抛出RuntimeException异常或者添加rollbackFor = Exception.class(也可以指定相应异常)
- 只有public修饰的方法才会生效。
- 在同一个类中,一个方法调用另外一个有注解(比如@Async,@Transational)的方法,注解是不会生效的。@Transactional注解在外部调用的函数上才有效果,内部调用的函数添加无效,这是由AOP的特性决定的。
原因:
spring 在扫描bean的时候会扫描方法上是否包含@Transactional注解,如果包含,spring会为这个bean动态地生成一个子类(即代理类,proxy),代理类是继承原来那个bean的。此时,当这个有注解的方法被调用的时候,实际上是由代理类来调用的,代理类在调用之前就会启动transaction。然而,如果这个有注解的方法是被同一个类中的其他方法调用的,那么该方法的调用并没有通过代理类,而是直接通过原来的那个bean,所以就不会启动transaction,我们看到的现象就是@Transactional注解无效。
- 当无事务方法调用有事务的方法时事务不会生效,而主方法有事务去调用其他方法,无论被调用的方法有无事务,且是否出现异常(有异常需要能够抛出不被捕获),都触发事务。
如何在事务方法中开启新事物:
- 开始aop事务,在启动类application上添加注解@EnableAspectJAutoProxy(exposeProxy = true)
- 子事务方法不直接调用,改为使用改方法的代理实现。
insertUser2();
((UserService)AopContext.currentProxy()).insertUser2();
这样会将当前事务挂起,重新开启另一个新的事务来执行语句子事务方法。
- 判断是否是代理类:
boolean isProxy = AopUtils.isAopProxy((UserService)AopContext.currentProxy());
注解总结:
@Transactional
注解只有作用到 public 方法上事务才生效,不推荐在接口上使用;- 避免同一个类中调用
@Transactional
注解的方法,这样会导致事务失效; - 正确的设置
@Transactional
的rollbackFor
和propagation
属性,否则事务可能会回滚失败; - 被
@Transactional
注解的方法所在的类必须被 Spring 管理,否则不生效; - 底层使用的数据库必须支持事务机制,否则不生效;
Spring + MyBatis-Plus 事务实现
在 Spring + MyBatis-Plus 项目中,事务管理主要通过 Spring 的声明式事务(@Transactional
)实现,结合 MyBatis-Plus 的数据源配置与事务管理器。以下是具体步骤和示例:
1. 添加依赖
确保项目中包含 spring-tx
、mybatis-plus-boot-starter
(Spring Boot)或 mybatis-spring
(传统 Spring)的依赖:
Maven 示例(Spring Boot):
xml
复制
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.3.1</version> </dependency> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId> </dependency>
运行 HTML
2. 配置数据源和事务管理器
Spring Boot 配置(application.yml
):
yaml
复制
spring:datasource:url: jdbc:mysql://localhost:3306/test?useSSL=falseusername: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Driver
事务管理器配置(Spring Boot 自动配置):
-
Spring Boot 会自动创建
DataSourceTransactionManager
,无需手动配置。 -
若需自定义事务管理器,可手动创建 Bean:
java
复制
@Configuration @EnableTransactionManagement // 启用事务管理 public class MyBatisPlusConfig {@Beanpublic PlatformTransactionManager transactionManager(DataSource dataSource) {return new DataSourceTransactionManager(dataSource);} }
3. 在 Service 层使用 @Transactional
在需要事务管理的方法或类上添加 @Transactional
注解:
java
复制
@Service public class UserServiceImpl implements UserService {@Autowiredprivate UserMapper userMapper;@Override@Transactional // 声明式事务public void updateUser(User user1, User user2) {userMapper.updateById(user1);int i = 1 / 0; // 模拟异常,触发事务回滚userMapper.updateById(user2);} }
4. 关键配置验证
确保以下配置正确:
-
数据源正确注入:MyBatis-Plus 的
SqlSessionFactory
需绑定到 Spring 的数据源。 -
事务管理器 Bean 存在:检查
DataSourceTransactionManager
是否被 Spring 容器管理。 -
@Transactional
生效范围:-
注解需添加到
public
方法上。 -
避免类内部方法调用(如
this.method()
),否则事务不生效(需通过代理对象调用)。
-
5. 事务回滚条件
-
默认回滚策略:当方法抛出
RuntimeException
或Error
时,事务自动回滚。 -
指定回滚异常:通过
rollbackFor
属性自定义:java
复制
@Transactional(rollbackFor = Exception.class)
6. MyBatis-Plus 特有配置
-
全局配置:在
MyBatisPlusConfig
中配置 MyBatis-Plus 的插件和策略(可选):java
复制
@Bean public MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new PaginationInnerInterceptor());return interceptor; }
常见问题排查
-
事务不生效:
-
检查是否添加了
@EnableTransactionManagement
。 -
确保方法为
public
,且未被同类内部方法调用。 -
确认数据库引擎支持事务(如 InnoDB)。
-
-
连接池配置:
-
建议使用
HikariCP
或Druid
作为数据源连接池。
-
总结
通过 @Transactional
+ DataSourceTransactionManager
,Spring + MyBatis-Plus 可轻松实现事务管理,确保数据库操作的原子性和一致性。