Spring 框架源码(二) 事务Transaction源码深度解析
目录
一、基于xml形式开启Transaction
1. 创建数据库user
2. 创建一个maven 项目
3. 通过xml形式配置事务
1) 创建Spring 命名空间
2) 开启事务配置
3) 创建UserService类
4. 测试事务
1) 抛出RuntimeException
2) 注释掉RuntimeException
二、事务开启入口TxNamespaceHandler
AnnotationDrivenBeanDefinitionParser
三、AOP驱动事务
TransactionInterceptor
创建事务
回滚事务
此文就Spring 事务实现流程进行源码解析,我们可以借此对Spring框架更多一层理解,下面以xml形式创建一个事务进行分析。
一、基于xml形式开启Transaction
1. 创建数据库user
/*Navicat Premium Data TransferSource Server : win-localSource Server Type : MySQLSource Server Version : 50737Source Host : localhost:3306Source Schema : db0Target Server Type : MySQLTarget Server Version : 50737File Encoding : 65001Date: 24/04/2022 20:27:41
*/SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,`age` int(11) NULL DEFAULT NULL,PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;SET FOREIGN_KEY_CHECKS = 1;
2. 创建一个maven 项目
不用Springboot依赖,引入mysql驱动依赖、spring-beans、spring-jdbc、Spring-context依赖
<dependencies><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.46</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-beans</artifactId><version>5.3.18</version></dependency><!-- spring jdbc 依赖spring-tx --><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>5.3.18</version></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-dbcp2</artifactId><version>2.7.0</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.18</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.2</version></dependency></dependencies>
3. 通过xml形式配置事务
1) 创建Spring 命名空间
首先在resources目录下创建一个spring.xml文件,Spring框架为了声明自己的Xml规范,在<beans>标签里定义了spring 框架指定模块的协议配置, 我们可以通过Index of /schema 访问spring框架的所有模块包,各模块包含了不同版本的xsd文件。
点击进入context目录,查看xsd文件:
比如我要通过xml的形式配置一个bean, 需要在beans标签中声明 xmln的值为:
http://www.springframework.org/schema/beans
如果我想用spring的context模块,那么需要声明
xmlns:context="http://www.springframework.org/schema/context"
同时在xsi: schemeLocation里添加context的url和spring-contxt.xsd的url:
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd
例如我创建一个能在xml中使用spring-beans模块,spring-txt模块,spring-context模块的配置如下:
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd
"></beans>
如果没有在beans标签里声明协议,那么在配置bean时会出现找不到指定标签的问题。
2) 开启事务配置
在spring.xml文件中添加配置事务配置,使用 annotation-driven 属性开启事务启动,
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" mode="proxy"/>
proxy-target-class默认为false, mode默认模式为proxy,可不用配置,待会从源码角度分析不同模式的事务开启。
接着配置transactionManager, 指定class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" mode="proxy"/><bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="ds"/></bean>
DataSourceTransactionManager里包含了DataSource属性配置:
因此我们需要接着配置数据源bean 别名为
<bean id="ds" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close"><property name="driverClassName" value="com.mysql.jdbc.Driver"/><property name="username" value="root"/><property name="password" value="root"/><property name="url" value="jdbc:mysql://localhost:3306/db0?useSSL=false"/><property name="initialSize" value="5"/><property name="maxIdle" value="2"/><property name="maxTotal" value="100"/></bean>
接着给Service配置一个bean, 引用dataSource数据源。
<!-- 配置bean,指定数据源--><bean id="userService" class="service.UserService"><property name="dataSource" ref="ds"/></bean>
3) 创建UserService类
通过dataSouce bean注入JDBCTemplate, 添加一个update(int id,String name)方法, 类上添加@Transactional(propagation = Propagation.REQUIRED)。
package service;import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;import javax.sql.DataSource;/*** @Desc:* @Author: bingbing* @Date: 2022/4/24 0024 21:39*/
@Transactional(propagation = Propagation.REQUIRED)
public class UserService {private JdbcTemplate jdbcTemplate;public void setDataSource(DataSource dataSource) {this.jdbcTemplate = new JdbcTemplate(dataSource);}public String getUserName(int id) {return jdbcTemplate.query("select * from db0.user where id= ?", rs -> rs.next() ? rs.getString(2) : "", new Object[]{id});}public void updateUser(int id, String name) {jdbcTemplate.update(" update user set name =? where id= ?", new Object[]{name, id});
// throw new RuntimeException("error!");}
}
4. 测试事务
使用ClassPathXmlApplicationContext类加载spring.xml文件
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import service.UserService;/*** @Desc:* @Author: bingbing* @Date: 2022/4/24 0024 21:42*/
public class UserServiceTests {@Testpublic void testTransaction() {ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");UserService userService = context.getBean("userService", UserService.class);String name = userService.getUserName(1);System.out.println("名字:"+name);userService.updateUser(1, "bing");String updateName = userService.getUserName(1);System.out.println("更新后的名字:" + updateName);}
}
数据库一条记录:
1) 抛出RuntimeException
update方法里放开//throw new RuntimeException("error!"); 注释,执行后
数据库里的记录没有修改,@Tranasctional注解生效。
2) 注释掉RuntimeException
重新执行后,观察结果
数据库也更新过来了。
前面的篇幅从xml的配置形式解释了Transaction集成过程,为什么要从xml形式入手transaction, 是为了后面阅读Spring-tx源码做准备。
二、事务开启入口TxNamespaceHandler
根据spring.xml文件里配置的tx:annitation-driven 关键字在Spring框架里全局搜索,找到目标类TxNamespaceHandler。位于spring-tx模块中的 org.springframework.transaction.config包下。
/** Copyright 2002-2012 the original author or authors.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package org.springframework.transaction.config;import org.w3c.dom.Element;import org.springframework.beans.factory.xml.NamespaceHandlerSupport;/*** {@code NamespaceHandler} allowing for the configuration of* declarative transaction management using either XML or using annotations.** <p>This namespace handler is the central piece of functionality in the* Spring transaction management facilities and offers two approaches* to declaratively manage transactions.** <p>One approach uses transaction semantics defined in XML using the* {@code <tx:advice>} elements, the other uses annotations* in combination with the {@code <tx:annotation-driven>} element.* Both approached are detailed to great extent in the Spring reference manual.** @author Rob Harrop* @author Juergen Hoeller* @since 2.0*/
public class TxNamespaceHandler extends NamespaceHandlerSupport {static final String TRANSACTION_MANAGER_ATTRIBUTE = "transaction-manager";static final String DEFAULT_TRANSACTION_MANAGER_BEAN_NAME = "transactionManager";static String getTransactionManagerName(Element element) {return (element.hasAttribute(TRANSACTION_MANAGER_ATTRIBUTE) ?element.getAttribute(TRANSACTION_MANAGER_ATTRIBUTE) : DEFAULT_TRANSACTION_MANAGER_BEAN_NAME);}@Overridepublic void init() {registerBeanDefinitionParser("advice", new TxAdviceBeanDefinitionParser());// 注册事务registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());registerBeanDefinitionParser("jta-transaction-manager", new JtaTransactionManagerBeanDefinitionParser());}}
找到了annotation-driven, 这个地方创建了一个AnnotationDrivenBeanDefinitionParser实例。
AnnotationDrivenBeanDefinitionParser
AnnotationDrivenBeanDefinitionParser 类的作用是解析spring.xml里的配置<tx:annotation-driven>标签,并根据配置的mode选择不同的模式取创建Transaction的整个初始化流程,此处也就是整个架Transaction架构的开始地方。
Spring事务注册的模式为动态代理模式,具体实现有2种: aspectj和proxy,可通过配置来选择使用那种形式的事务注册, 如果不配置mode那么使用默认的proxy形式创建,如果我们要使用aspectj模式开启事务,那么就配置mode="aspectj"。
<tx:annotation-driven transaction-manager="transactionManager" mode="aspectj">
我们可以看到Spring事务的开启是默认是以AOP为基础的。
三、AOP驱动事务
AopAutoProxyConfigurer 的configureAutoProxyCreator方法注册了3个Bean, 该3个Bean 是驱动Spring 事务架构的核心支柱,分别是TransactionAttributeSource、TransactionInterceptor、TransactionAttributeSourceAdvisor。
private static class AopAutoProxyConfigurer {public static void configureAutoProxyCreator(Element element, ParserContext parserContext) {AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element);String txAdvisorBeanName = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME;if (!parserContext.getRegistry().containsBeanDefinition(txAdvisorBeanName)) {Object eleSource = parserContext.extractSource(element);// Create the TransactionAttributeSource definition.RootBeanDefinition sourceDef = new RootBeanDefinition("org.springframework.transaction.annotation.AnnotationTransactionAttributeSource");sourceDef.setSource(eleSource);sourceDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);String sourceName = parserContext.getReaderContext().registerWithGeneratedName(sourceDef);// Create the TransactionInterceptor definition.RootBeanDefinition interceptorDef = new RootBeanDefinition(TransactionInterceptor.class);interceptorDef.setSource(eleSource);interceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);registerTransactionManager(element, interceptorDef);interceptorDef.getPropertyValues().add("transactionAttributeSource", new RuntimeBeanReference(sourceName));String interceptorName = parserContext.getReaderContext().registerWithGeneratedName(interceptorDef);// Create the TransactionAttributeSourceAdvisor definition.RootBeanDefinition advisorDef = new RootBeanDefinition(BeanFactoryTransactionAttributeSourceAdvisor.class);advisorDef.setSource(eleSource);advisorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);advisorDef.getPropertyValues().add("transactionAttributeSource", new RuntimeBeanReference(sourceName));advisorDef.getPropertyValues().add("adviceBeanName", interceptorName);if (element.hasAttribute("order")) {advisorDef.getPropertyValues().add("order", element.getAttribute("order"));}// 事务通知器 transaction advisor, 基于AOP实现的advisorparserContext.getRegistry().registerBeanDefinition(txAdvisorBeanName, advisorDef);CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), eleSource);compositeDef.addNestedComponent(new BeanComponentDefinition(sourceDef, sourceName));compositeDef.addNestedComponent(new BeanComponentDefinition(interceptorDef, interceptorName));compositeDef.addNestedComponent(new BeanComponentDefinition(advisorDef, txAdvisorBeanName));parserContext.registerComponent(compositeDef);}}}
其中TransactionInterceptor是Spring事务的目标方法的增强,通过代理完成Spring 事务的提交、异常处理和回滚。
TransactionInterceptor
TransactionInterceptor是Spring 事务对目标方法的增强器,说简单点就是一层代理,基于Aop实现,实现了spring-aop的Advice接口,同时实现了IntializingBean和BeanFactoryAware接口,只要有事务的执行,那么目标方法的调用类在invoke()方法会生成一个代理对象,通过invoke()方法对目标调用方法进行增强。
@FunctionalInterface
public interface MethodInterceptor extends Interceptor {/*** Implement this method to perform extra treatments before and* after the invocation. Polite implementations would certainly* like to invoke {@link Joinpoint#proceed()}.* @param invocation the method invocation joinpoint* @return the result of the call to {@link Joinpoint#proceed()};* might be intercepted by the interceptor* @throws Throwable if the interceptors or the target object* throws an exception*/Object invoke(MethodInvocation invocation) throws Throwable;}
TransactionInterceptor的invoke()实现:
@Override@Nullablepublic Object invoke(final MethodInvocation invocation) throws Throwable {// Work out the target class: may be {@code null}.// The TransactionAttributeSource should be passed the target class// as well as the method, which may be from an interface.Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);// Adapt to TransactionAspectSupport's invokeWithinTransaction...return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);}
创建事务
Spring 创建事务的方式有二种: 声明式事务和编程式事务, 我们可以通过分析一种理解核心流程和原理即可。
进入invokeWithinTransaction()方法直接看声明式事务执行过程的源代码:
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {// Standard transaction demarcation with getTransaction and commit/rollback calls.// 创建事务TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);Object retVal = null;try {// This is an around advice: Invoke the next interceptor in the chain.// This will normally result in a target object being invoked.retVal = invocation.proceedWithInvocation();} catch (Throwable ex) {// target invocation exception// 根据指定异常进行回滚。completeTransactionAfterThrowing(txInfo, ex);throw ex;} finally {cleanupTransactionInfo(txInfo);}// 提交事务commitTransactionAfterReturning(txInfo);return retVal;}
invokeWithinTransaction方法做了哪些事?
1) 通过createTransactionIfNecessary方法创建一个事务,相当于此处开启一个事务。
2) invocation.proceedWithInvocation() 执行目标方法调用。
3) 如果出现异常,那么completeTransactionAfterThrowing处理异常。
4) 在finally 清除掉transaction相关的信息,同时在commitTransactionAfterReturning 提交事务。
回滚事务
我们可以从上面的源码中发现通过transactionManager执行回滚操作。
txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
Spring 框架源码(二) 事务Transaction源码深度解析相关推荐
- 《Spring》第二十一篇 事务底层源码解析
目录 一.事务的底层执行原理 1. 解析注解@EnableTransactionManagement 2. Bean生命周期的初始化后阶段,校验是否需要进行AOP 3. 示例FruitsService ...
- Spring框架学习(二)
Spring框架学习笔记---->AOP AOP概念 AOP:aspect oriented programing,面向切面编程. 将软件的各个模块,按照横向的角度进行观察,发现各个模块之间存在 ...
- spring框架学习(二)依赖注入
spring框架为我们提供了三种注入方式,分别是set注入,构造方法注入,接口注入.接口注入不作要求,下面介绍前两种方式. 1,set注入 采用属性的set方法进行初始化,就成为set注入. 1)给普 ...
- python django事务transaction源码分析
2019独角兽企业重金招聘Python工程师标准>>> python Django事务 网上关于django1.6的事务资料很多,但是1.8的却搜不到任何资料,自己要用的时候费了不少 ...
- Spring 框架基础(05):事务管理机制,和实现方式
本文源码:GitHub·点这里 || GitEE·点这里 一.Spring事务管理 1.基础描述 Spring事务管理的本质就是封装了数据库对事务支持的操作,使用JDBC的事务管理机制,就是利用jav ...
- stust2 hibernate spring 框架复习(二)
上节主要讲了stust2的相关配置,本节将讨论代码实现的原理 第二节:Stust2 登录验证实例代码详解 首先,我们先理清文件结构: 从java resources开始从上到下,有用的文件为: src ...
- Spring框架笔记(二十二)——切点表达式的重用
2019独角兽企业重金招聘Python工程师标准>>> 在编写 AspectJ 切面时, 可以直接在通知注解中书写切入点表达式. 但同一个切点表达式可能会在多个通知中重复出现. 在 ...
- Struts1.2 Hibernate Spring框架组合(二)
搭建好SSH的组合环境,简单的开发一个东西,一个页面输入用户的编号和用户名,年龄来进行页面的跳转,还用查询所有的对象信息. 安装Hibernate的真正意义是不用手动建数据库表的,只需要创建好实体类就 ...
- spring框架:(二)bean标签中的scop、生命周期以及注入方式
1.bean标签的scop属性: 作用:用于指定对应bean的作用范围 取值: singleton:单例模式(默认): prototype:多例模式: request:作用于web应 ...
最新文章
- 北大新研究用数学模型揭网红崛起奥秘!登上Nature子刊
- 【带你重拾Redis】Redis持久化
- Android面试题目之(13) ListView的设计原理
- 设备和驱动在第一次加载,会遍历总线
- 把 SOAP 服务转化为 REST 服务(REST Service 的最佳实践,第 3 部分)
- SQL——将表中的最大ID+1插入新的ID中------Insert into 表 MAX(表id) +1
- Node.js 应用的内存泄漏问题的检测方法
- 【问题汇总】ListView的FooterView设置可见性的问题
- 【Java线程】“打工人”初识线程池及自定义线程池实战
- 国内物联网平台初探 ——移动云OneNET
- 快速上手Expression Tree(一):做一做装配脑袋的Expression Tree 习题
- 35张动图:关于机房空调水冷系统和风冷系统
- 马尔可夫链、隐马尔科夫模型、贝叶斯网络、因子图
- 计算机无法安装蓝牙驱动,win7电脑蓝牙驱动怎么安装,教您怎么安装
- css内联样式---动态绑定背景图片
- 美团23届秋招全面启动!5000+机会,60+岗位,3次笔试机会!
- 2021-08-01 半夜睡不着系列之光纤接口类型
- 为防止用户自定义Bixby按键,三星先给T-Mobile用户的S8发送更新
- 程序集定义(Assembly Definition File)
- sfc /scannow是什么意思,有什么作用?(转载)
热门文章
- 输入框实时搜索优化,减少请求次数、取消无用请求
- 孩子的世界20060303
- JAVA基础练习:应用数组实现杨辉三角输出3.5
- javascript 常用的时间函数
- 米拓CMS插件推荐免登陆采集发布插件
- 语言 ,规范,协议,框架
- 学习笔记:SpringCloud 微服务技术栈_高级篇⑤_可靠消息服务
- 磁盘 扇区 柱面 linux,磁盘的基本知识:磁道、柱面和扇区
- [附源码]计算机毕业设计Python+uniapp家电维修微信小程序z8jl5(程序+lw+远程部署)
- python 实现将图像转化为位图数组