文章目录
1. 事务管理的切面本质
事务管理在
Spring
中的实现依赖于
AOP
,而
AOP
主要通过代理机制来实现。具体而言,
AOP
通过代理对象拦截对目标方法的调用,并在方法调用前后插入事务管理逻辑。拦截器机制是
AOP
实现的一部分,用于定义具体的增强逻辑。
Spring
能够通过
AOP
将事务管理逻辑独立出来,作为一个切面应用于所有标注了
@Transactional
的方法。代理对象会拦截对这些方法的调用,并在方法执行前后插入事务管理逻辑。
在事务管理中,拦截器机制起到了关键作用。
Spring
使用
TransactionInterceptor
拦截器来处理事务逻辑。
TransactionInterceptor
在方法调用之前开启事务,在方法执行过程中进行事务管理,并在方法调用结束后根据执行结果提交或回滚事务。这种机制确保了事务管理逻辑与业务逻辑的解耦,提高了代码的可维护性和可读性。
2. 用AOP手动实现 @Transactional 基本功能
为了能更清晰的理解到
@Transactional
本质就是
AOP
,我们自行实现一个
@MyTransactional
注解,来简单模拟
@Transactional
的功能。
我这里为了演示
API
的使用,在代码演示的时候会设置事务的隔离级别为
READ_COMMITTED
,设置事务的传播行为为
REQUIRES_NEW
全部代码如下:
- 数据库表创建
首先,创建
test
表:
CREATETABLE`test`(`id`bigintunsignedNOTNULLAUTO_INCREMENT,`name`varchar(255)NOTNULLDEFAULT'',PRIMARYKEY(`id`))ENGINE=InnoDBAUTO_INCREMENT=1DEFAULTCHARSET=utf8mb4
- 配置数据源、
MyBatis
和事务管理器
packagecom.example.demo.configuration;importorg.apache.ibatis.session.SqlSessionFactory;importorg.mybatis.spring.SqlSessionFactoryBean;importorg.mybatis.spring.SqlSessionTemplate;importorg.mybatis.spring.annotation.MapperScan;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.ComponentScan;importorg.springframework.context.annotation.Configuration;importorg.springframework.context.annotation.EnableAspectJAutoProxy;importorg.springframework.core.io.support.PathMatchingResourcePatternResolver;importorg.springframework.jdbc.datasource.DataSourceTransactionManager;importorg.springframework.jdbc.datasource.DriverManagerDataSource;importorg.springframework.transaction.PlatformTransactionManager;importjavax.sql.DataSource;@Configuration@ComponentScan(basePackages ="com.example.demo")@MapperScan("com.example.demo.mapper")@EnableAspectJAutoProxypublicclassAppConfig{@BeanpublicDataSourcedataSource(){DriverManagerDataSource dataSource =newDriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/demo");
dataSource.setUsername("root");
dataSource.setPassword("password");return dataSource;}@BeanpublicSqlSessionFactorysqlSessionFactory(DataSource dataSource)throwsException{SqlSessionFactoryBean sqlSessionFactoryBean =newSqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
sqlSessionFactoryBean.setMapperLocations(newPathMatchingResourcePatternResolver().getResources("classpath*:mybatis/**/*.xml"));return sqlSessionFactoryBean.getObject();}@BeanpublicSqlSessionTemplatesqlSessionTemplate(SqlSessionFactory sqlSessionFactory){returnnewSqlSessionTemplate(sqlSessionFactory);}@BeanpublicPlatformTransactionManagertransactionManager(DataSource dataSource){returnnewDataSourceTransactionManager(dataSource);}}
- 创建
Mapper
接口和XML
映射文件
这里
TestMapper.java
和
TestMapper.xml
定义了数据库操作方法。
TestMapper.java:
packagecom.example.demo.mapper;importcom.example.demo.model.Test;publicinterfaceTestMapper{// 定义插入Test的方法voidinsertTest(Test test);// 定义根据ID查询Test的方法TestselectTestById(Long id);}
TestMapper.xml:
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPEmapperPUBLIC"-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mappernamespace="com.example.demo.mapper.TestMapper"><!-- 插入记录到test表 --><insertid="insertTest"parameterType="com.example.demo.model.Test">
INSERT INTO test (name) VALUES (#{name})
</insert><!-- 根据ID查询test表中的记录 --><selectid="selectTestById"parameterType="long"resultType="com.example.demo.model.Test">
SELECT * FROM test WHERE id = #{id}
</select></mapper>
- 创建模型类
Test.java
定义了数据表对应的实体类。
Test.java:
packagecom.example.demo.model;// 定义Test类与数据库表test对应publicclassTest{privateLong id;// 主键IDprivateString name;// 名称// 获取IDpublicLonggetId(){return id;}// 设置IDpublicvoidsetId(Long id){this.id = id;}// 获取名称publicStringgetName(){return name;}// 设置名称publicvoidsetName(String name){this.name = name;}}
- 创建自定义事务注解
MyTransactional.java:
packagecom.example.demo.annotation;importjava.lang.annotation.ElementType;importjava.lang.annotation.Retention;importjava.lang.annotation.RetentionPolicy;importjava.lang.annotation.Target;// 自定义事务注解@Target({ElementType.METHOD,ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)public@interfaceMyTransactional{}
- 创建事务管理切面
TransactionAspect.java:
packagecom.example.demo.aspect;importorg.aspectj.lang.ProceedingJoinPoint;importorg.aspectj.lang.annotation.Around;importorg.aspectj.lang.annotation.Aspect;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Component;importorg.springframework.transaction.PlatformTransactionManager;importorg.springframework.transaction.TransactionStatus;importorg.springframework.transaction.support.DefaultTransactionDefinition;@Aspect// 声明这是一个切面类@Component// 将这个类标记为 Spring 组件publicclassTransactionAspect{@Autowired// 注入事务管理器privatePlatformTransactionManager transactionManager;// 环绕通知,拦截所有标注了 @MyTransactional 的方法@Around("@annotation(com.example.demo.annotation.MyTransactional)")publicObjectaround(ProceedingJoinPoint pjp)throwsThrowable{// 创建默认事务定义DefaultTransactionDefinition def =newDefaultTransactionDefinition();// 设置事务的传播行为为REQUIRES_NEW
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);// 设置事务的隔离级别为READ_COMMITTED
def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);// 获取事务状态对象,开启事务TransactionStatus status = transactionManager.getTransaction(def);Object result;try{
result = pjp.proceed();// 执行目标方法
transactionManager.commit(status);// 提交事务}catch(Throwable ex){
transactionManager.rollback(status);// 回滚事务throw ex;}return result;// 返回结果}}
- 创建
Service
层
TestService.java:
packagecom.example.demo.service;importcom.example.demo.mapper.TestMapper;importcom.example.demo.model.Test;importcom.example.demo.annotation.MyTransactional;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Service;@Service// 标记这个类为 Spring 服务组件publicclassTestService{@Autowired// 注入 TestMapperprivateTestMapper testMapper;// 使用自定义的 @MyTransactional 注解@MyTransactionalpublicvoidcreateTest(Test test){
testMapper.insertTest(test);// 插入记录// 模拟异常以测试事务回滚if("error".equals(test.getName())){thrownewRuntimeException("Simulated error");}}// 根据 ID 查询记录publicTestgetTestById(Long id){return testMapper.selectTestById(id);}}
Main
方法
创建一个
Main
方法来启动
Spring
应用程序并执行测试逻辑:
MainApp.java:
packagecom.example.demo;importcom.example.demo.configuration.AppConfig;importcom.example.demo.model.Test;importcom.example.demo.service.TestService;importorg.springframework.context.ApplicationContext;importorg.springframework.context.annotation.AnnotationConfigApplicationContext;publicclassDemoApplication{publicstaticvoidmain(String[] args){// 加载 Spring 配置文件ApplicationContext context =newAnnotationConfigApplicationContext(AppConfig.class);// 获取 TestService BeanTestService testService = context.getBean(TestService.class);try{Test test =newTest();
test.setName("success");
testService.createTest(test);System.out.println("Test with name 'success' created.");}catch(Exception e){System.out.println("Failed to create test with name 'success': "+ e.getMessage());}try{Test test =newTest();
test.setName("error");
testService.createTest(test);}catch(Exception e){System.out.println("Failed to create test with name 'error': "+ e.getMessage());}}}
执行结果如下:
注意:使用
DefaultTransactionDefinition
来设置事务的隔离级别和传播行为时,这些设置是针对当前事务的,而不是针对整个数据库会话的。具体来说,事务的隔离级别和传播行为是在事务管理器(例如
DataSourceTransactionManager
)控制的事务范围内有效。
当设置事务的隔离级别为
READ_COMMITTED
时,实际上是针对当前事务的连接设置的。这意味着在当前事务的生命周期内(从事务开始到提交或回滚),该隔离级别是有效的。一旦事务结束,连接的隔离级别会被重置为默认值。
在
JDBC
和
Spring
框架中,事务的隔离级别通常用整数来表示。以下是这些隔离级别及其对应的整数值:
- TransactionDefinition.ISOLATION_DEFAULT (0): 默认隔离级别,这个值依赖于底层数据库的默认设置。
- TransactionDefinition.ISOLATION_READ_UNCOMMITTED (1): 读未提交。这是最低的隔离级别,允许读取未提交的数据,可能会导致脏读。
- TransactionDefinition.ISOLATION_READ_COMMITTED (2): 读已提交。只允许读取已提交的数据,可以防止脏读。
- TransactionDefinition.ISOLATION_REPEATABLE_READ (4): 可重复读。保证在同一个事务中多次读取相同数据的结果是一致的,防止不可重复读。
- TransactionDefinition.ISOLATION_SERIALIZABLE (8): 串行化。这是最高的隔离级别,确保事务完全串行化执行,防止脏读、不可重复读和幻读,但性能较低。
3. PlatformTransactionManager的设计
从上面可以看到,提交事务和回滚事务都是由
PlatformTransactionManager
对象操作的。
PlatformTransactionManager
是做什么的?
在日常开发中,虽然我们通常使用
@Transactional
注解来管理事务,但实际上
PlatformTransactionManager
在后台起着关键作用。
Spring
的事务管理是通过代理机制和
AOP
(面向切面编程)来实现的,
PlatformTransactionManager
就是在这个过程中被使用的核心组件。
PlatformTransactionManager
接口定义了以下主要方法:
1. getTransaction(TransactionDefinition definition):
- 这是用于开始一个新的事务或返回当前事务的句柄的方法。它根据传入的
TransactionDefinition
(事务定义)参数来决定是否需要开启一个新事务,或者参与一个现有的事务。比如,若是REQUIRES_NEW
,代表如果没有事务存在,它会创建一个新的事务。 TransactionDefinition
参数包括事务的传播行为、隔离级别、超时时间和是否只读等属性。
句柄(
handle
)在编程中的作用可以理解为一种引用或者标识符,也可以理解为指针。用来间接地访问某个资源或对象。句柄的目的是简化和抽象资源管理,不需要直接操作底层资源,只需要通过句柄来进行操作。
Spring
事务管理中,句柄用来表示事务的状态和上下文。这个句柄并不是实际的事务对象,而是对事务的一种引用或标识,使得
Spring
框架可以管理事务的生命周期。
现实生活中的类比
图书馆借书卡:
- 在图书馆中,你有一张借书卡,这张借书卡上有一个唯一的编号。
- 借书卡相当于一个句柄,它并不直接代表你借的书,而是代表在图书馆的借书权限和记录。
- 当你想借书或还书时,只需要出示这张借书卡,图书馆的系统会通过借书卡的编号找到你的借书记录,完成借书或还书操作。
在编程中,句柄扮演的角色类似于借书卡的编号,通过句柄可以间接地操作或访问实际的资源。
2. commit(TransactionStatus status):
- 提交给定的事务,
TransactionStatus
包含了事务的具体信息。 - 如果事务被成功提交,则对数据库的所有更改将被保存。
3. rollback(TransactionStatus status):
- 回滚给定的事务,
TransactionStatus
包含了事务的具体信息。 - 如果事务被回滚,则对数据库的所有更改将被撤销。
4. 事务拦截器TransactionInterceptor
在
Spring
中,事务拦截器是通过
AOP
(面向切面编程)机制实现的。事务拦截器会拦截标注了
@Transactional
注解的方法调用,并在方法执行前后管理事务。
- 事务管理的启用
在
Spring
应用中,事务管理通常通过
@EnableTransactionManagement
注解启用,这会注册必要的基础设施来处理事务。
@Configuration@EnableTransactionManagementpublicclassAppConfig{// 配置数据源和事务管理器}
- 事务拦截器的配置
@EnableTransactionManagement
会配置一个事务顾问(
advisor
)和一个事务拦截器(
interceptor
)。这些组件在
Spring
容器启动时被注册。
关键类和接口
- TransactionManagementConfigurationSelector:负责选择启用事务管理所需的配置类。
- ProxyTransactionManagementConfiguration:具体配置类,负责注册事务管理相关的
bean
。 - TransactionInterceptor:事务拦截器,实际负责事务管理的逻辑。
TransactionInterceptor的工作原理
TransactionInterceptor
是
Spring
事务管理的核心组件之一,它实现了
MethodInterceptor
接口,通过
AOP
拦截标注了
@Transactional
的方法,并在方法执行前后进行事务管理。
TransactionInterceptor的源码位置
TransactionInterceptor
类位于
org.springframework.transaction.interceptor
包中。以下是其关键部分的简化代码和解释。
packageorg.springframework.transaction.interceptor;importorg.aopalliance.intercept.MethodInterceptor;importorg.aopalliance.intercept.MethodInvocation;importorg.springframework.transaction.PlatformTransactionManager;importorg.springframework.transaction.TransactionStatus;importorg.springframework.transaction.support.TransactionCallback;importorg.springframework.transaction.support.TransactionTemplate;publicclassTransactionInterceptorextendsTransactionAspectSupportimplementsMethodInterceptor{@OverridepublicObjectinvoke(MethodInvocation invocation)throwsThrowable{// 获取目标方法Method method = invocation.getMethod();// 获取事务属性TransactionAttributeSource tas =getTransactionAttributeSource();TransactionAttribute txAttr = tas.getTransactionAttribute(method, invocation.getThis().getClass());// 获取事务管理器PlatformTransactionManager tm =determineTransactionManager(txAttr);// 如果没有事务属性或事务管理器,则直接执行方法if(txAttr ==null|| tm ==null){return invocation.proceed();}// 创建事务模板TransactionTemplate tt =createTransactionTemplate(tm, txAttr);// 开启事务,执行目标方法,并管理事务提交或回滚return tt.execute(newTransactionCallback<Object>(){@OverridepublicObjectdoInTransaction(TransactionStatus status){try{return invocation.proceed();}catch(Throwable ex){// 回滚事务
status.setRollbackOnly();thrownewRuntimeException(ex);}}});}}
时序图如下:
过程描述
- 客户端调用目标方法:
- 客户端发起对目标方法的调用。
- 这一调用被
TransactionInterceptor
拦截。
- 获取事务属性:
TransactionInterceptor
通过TransactionAttributeSource
获取目标方法的事务属性。- 事务属性包括事务的传播行为、隔离级别、超时设置等。
TransactionAttributeSource
返回获取到的事务属性。
- 确定事务管理器:
TransactionInterceptor
确定要使用的PlatformTransactionManager
。
- 获取事务管理器:
- 根据事务属性获取适当的事务管理器。
- 事务管理器负责管理事务的生命周期(开始、提交、回滚)。
- 检查事务属性和事务管理器:
TransactionInterceptor
检查是否获取到事务属性和事务管理器。- 如果没有获取到事务属性或事务管理器,则直接执行目标方法,不进行事务管理。
TransactionInterceptor
调用目标方法并返回执行结果。
- 创建事务模板:
- 如果获取到事务属性和事务管理器,
TransactionInterceptor
创建一个TransactionTemplate
。 TransactionTemplate
用于简化事务操作。
- 使用事务模板执行目标方法:
TransactionInterceptor
使用TransactionTemplate
开启事务并执行目标方法。
- 方法执行成功:
- 如果目标方法执行成功,事务模板提交事务。
- 事务模板返回执行结果。
TransactionInterceptor
将结果返回给客户端。
- 方法执行抛出异常:
- 如果目标方法抛出异常,事务模板回滚事务。
- 事务模板抛出异常。
TransactionInterceptor
将异常抛出给客户端。
**当
Spring
启动时,
@EnableTransactionManagement
启用事务管理,
Spring
会自动配置事务拦截器
TransactionInterceptor
。当调用标注了
@Transactional
的方法时,
TransactionInterceptor
会拦截该方法,并通过
PlatformTransactionManager
管理事务的开启、提交和回滚。**
总结
@EnableTransactionManagement
启用事务管理,并注册必要的事务管理组件。TransactionInterceptor
通过AOP
拦截事务方法,并管理事务的开启、提交和回滚。PlatformTransactionManager
在后台执行实际的事务操作。
有兴趣的小伙伴可以自行去学习了解
TransactionInterceptor
如何处理事务,以及在事务属性解析、事务管理器选择等方面的具体实现。
5. 时序图展示事务管理流程
这里展示处理事务时的作用流程,包含多种事务传播行为的判断。
详细步骤解释
1. 调用带有@Transactional的方法:
- 客户端调用标注了
@Transactional
的方法。 AOP
拦截器拦截该方法调用。
2. 获取事务:
AOP
拦截器,也就是事务拦截器,调用PlatformTransactionManager
的getTransaction(TransactionDefinition)
方法获取事务。- 事务管理器检查当前事务上下文,确定是否需要创建新事务或加入现有事务。
3. 处理传播行为:
- 根据
TransactionDefinition
中的传播行为(Propagation Behavior
)决定事务的处理方式。 REQUIRED
:如果没有现有事务,创建新事务;如果已有事务,加入现有事务。REQUIRES_NEW
:总是创建新事务,如果已有事务,则挂起当前事务。SUPPORTS
:如果有现有事务,则加入现有事务;如果没有事务,则以非事务方式执行。NOT_SUPPORTED
:如果有现有事务,则挂起当前事务,以非事务方式执行。MANDATORY
:如果没有现有事务,则抛出异常。NEVER
:如果有现有事务,则抛出异常。NESTED
:如果有现有事务,则创建嵌套事务;如果没有事务,则创建新事务。
4. 执行目标方法:
AOP
拦截器通知客户端继续执行业务逻辑。- 客户端执行数据库操作(例如保存实体)。
5. 提交或回滚事务:
如果业务逻辑执行过程中抛出异常:
AOP
拦截器调用事务管理器的rollback(TransactionStatus)
方法。- 事务管理器执行回滚操作,并返回回滚状态。
AOP
拦截器将异常抛回给客户端。
如果业务逻辑正常执行完毕:
AOP
拦截器调用事务管理器的commit(TransactionStatus)
方法。- 事务管理器执行提交操作,并返回提交状态。
AOP
拦截器将结果返回给客户端。
6. 事务与线程的关系——@Transactional方法内的异步线程
在写业务的时候可以会遇到,一个方法内部有数据库操作和异步线程的操作,如果是数据库操作的异常,这肯定会回滚,如果数据库操作正常,异步线程执行的时候出现异常,数据库操作会回滚吗?
用一个例子简洁的说明
@ServicepublicclassOrderService{@AutowiredprivateOrderRepository orderRepository;@TransactionalpublicvoidplaceOrder(Order order){// 保存订单
orderRepository.save(order);// 启动异步任务CompletableFuture.runAsync(()->{try{// 模拟异步任务抛出异常if(true){thrownewRuntimeException("异步任务出错");}}catch(Exception e){// 异常处理逻辑// 记录日志,发送告警等System.err.println("异步任务异常: "+ e.getMessage());}});// 其他业务逻辑}}
答案如下:
在
@Transactional
方法中直接启动的异步线程如果抛出异常,不会导致整个事务回滚。事务的边界仅限于当前线程,异步线程中的异常不会影响主事务。
事务和线程的关系如下
- 事务绑定到线程上下文:
Spring
的事务管理是基于TransactionSynchronizationManager
来管理事务状态的。- 每个事务在当前线程中开始,事务状态(如是否提交或回滚)存储在当前线程的
ThreadLocal
变量中。这意味着事务的边界是线程级的。
- @Transactional的工作原理:
- 当一个方法被
@Transactional
注解修饰时,Spring
会通过AOP
创建一个代理,该代理会在方法调用之前开启事务,在方法完成之后根据方法执行情况提交或回滚事务。 - 事务的开始和结束都发生在当前线程中,即事务的边界局限于执行该方法的线程。
异步线程与事务的独立性
- 异步任务在不同的线程中执行:
- 异步任务通常使用
@Async
注解、CompletableFuture.runAsync
、new Thread
或自定义线程池来启动。无论哪种方式,异步任务会在一个新的线程中运行。 - 新线程没有继承原始线程的
ThreadLocal
变量,包括事务上下文,因此异步线程不参与主事务。
- 事务状态的隔离:
- 主线程中执行的事务与异步线程中的操作是独立的。即使异步线程中抛出异常,也不会影响主线程的事务状态。
- 异步线程中的异常只会在异步线程中传播,不会传播回主线程。
总结
Spring
的事务管理是线程绑定的,每个事务与启动它的线程绑定。- 异步任务在不同的线程中执行,不继承主线程的事务上下文。
- 异步线程中的异常不会影响主线程的事务状态。
7. @Transactional 代理机制源码引导分析
一旦分析源码,文章的可阅读性就降低了,这里留有有兴趣的小伙伴自行阅读。
在
Spring
框架中,
@Transactional
的代理机制主要涉及几个关键类和接口,包括
TransactionInterceptor
、
ProxyTransactionManagementConfiguration
、
TransactionAttributeSourceAdvisor
和
ProxyFactoryBean
等。下面是一些关键的源码位置:
TransactionInterceptor:这个类位于
org.springframework.transaction.interceptor
包中,它是事务管理的核心拦截器。
TransactionAttributeSourceAdvisor:这个类位于
org.springframework.transaction.annotation
包中,它是
AOP Advisor
,负责根据事务注解创建切点。
ProxyTransactionManagementConfiguration:这个类位于
org.springframework.transaction.annotation
包中,它是事务管理配置的核心,负责注册事务管理相关的
bean
。
可以从
@EnableTransactionManagement
注解开始追踪源码,该注解引导到
ProxyTransactionManagementConfiguration
类,该类配置了事务管理的
AOP
切面。
欢迎一键三连~
有问题请留言,大家一起探讨学习
----------------------Talk is cheap, show me the code-----------------------
版权归原作者 砖业洋__ 所有, 如有侵权,请联系我们删除。