文档章节

揭开Spring事务处理

东小天
 东小天
发布于 2016/07/11 16:07
字数 1972
阅读 4
收藏 0
点赞 0
评论 0

本文对Spring实现事务处理的真正原理进行追究,从而从中提炼出一些见解。其中讲解内容可能会存在一定的误导,还希望指出,内容仅供参考!(经过本人后期继续研读Spring关于Mybatis的事务处理,其实在mybatis的里面调用了spring的方法来获取Connection,所以本文所提供的一种实现,是另一种Spring的实现猜想,仅供参考!)

  说到Spring事务原理,百度一下最多的就是Spring的AOP了,本文当然不是给你将AOP的原理,如果是这样,我也就没必要写这篇文章了,直接转载一篇就行了。借助Spring的AOP的原理,提出一个问题。

            此处先粘贴出Spring事务需要的配置内容:

 

  1.        <bean id="transactionManager"  
  2. class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
  3. <property name="dataSource" ref="dataSource" />.....  
  4. lt;/bean>  

 

  1.         <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">  
  2. <property name="dataSource" ref="dataSource" />  
  3. .....         
  4. lt;/bean>  

            上面两段配置文件一个是Spring事务管理器的配置文件,另一个是一个普通的JPA框架(此处是mybatis)的配置文件,这两个里面都配置了datasource,而且这个datasource的对象是在Spring的容器里面。一下提几个问题:

            1、当JPA框架对数据库进行操作的时候,是从那里获取Connection?

            2、jdbc对事务的配置,比如事务的开启,提交以及回滚是在哪里设置的?

            3、Spring是通过aop拦截切面的所有需要进行事务管理的业务处理方法,那如何获取业务处理方法里面对数据库操作的事务呢?

           现在我来对上面的问题来一一回答

           1、这个问题很简单,既然在JPA的框架里面配置了datasource,那自然会从这个datasource里面去获得连接。

           2、jdbc的事务配置是在Connection对消里面有对应的方法,比如setAutoCommit,commit,rollback这些方法就是对事务的操作。

           3、Spring需要操作事务,那必须要对Connection来进行设置。Spring的AOP可以拦截业务处理方法,并且也知道业务处理方法里面的DAO操作的JAP框架是从datasource里面获取Connection对象,那么Spring需要对当前拦截的业务处理方法进行事务控制,那必然需要得到他内部的Connection对象。整体的结构图如下:

           

           上图是一个标准的业务处理在一个线程上的基本流程。JPA框架在需要对数据库进行操作的时候,就会从Datasource里面去获取Connection对象,那么Spring是怎么样拿到在JPA内部调用的Connection并且加上用户配置的事务处理规则的呢?现在我来揭开这个谜底。

           上面也看到注入到Spring的事务管理的datasource和注入到第三方JPA框架的datasource都是在Spring容器里面的,并且是同一个对象。既然Spring可以拿到你这个datasource对象,那它为什么不进行一下封装呢?不管是哪家的Datasource他们都会实现javax.sql.DataSource这个接口,这个接口里面主要有两个方法

 

  1. <span style="font-size:18px">public interface DataSource  extends CommonDataSource,Wrapper {  
  2.   
  3.   /** 
  4.    * <p>Attempts to establish a connection with the data source that 
  5.    * this <code>DataSource</code> object represents. 
  6.    * 
  7.    * @return  a connection to the data source 
  8.    * @exception SQLException if a database access error occurs 
  9.    */  
  10.   Connection getConnection() throws SQLException;  
  11.         
  12.   /** 
  13.    * <p>Attempts to establish a connection with the data source that 
  14.    * this <code>DataSource</code> object represents. 
  15.    * 
  16.    * @param username the database user on whose behalf the connection is  
  17.    *  being made 
  18.    * @param password the user's password 
  19.    * @return  a connection to the data source 
  20.    * @exception SQLException if a database access error occurs 
  21.    * @since 1.4 
  22.    */  
  23.   Connection getConnection(String username, String password)   
  24.     throws SQLException;</span><span style="font-size:18px">  
  25. </span>  


              这两个方法均是获取Connection对象的。Spring有没有可能对这个接口创建一个代理呢?通过spring的AOP。然后偷偷将Spring容器里面的datasource的bean指向这个代理对象(此处称该对象为datasourceproxy,替换之前的叫datasource)。于是不管是从哪里调用Datasource,那必然会被Spring拦截。下面是模拟了一个简单实现

  1. public class DatasourceHandler implements InvocationHandler {  
  2.   
  3.     private DataSource dataSource;  
  4.     /** 
  5.      * @param dataSource 
  6.      */  
  7.     public DatasourceHandler(DataSource dataSource) {  
  8.         super();  
  9.         this.dataSource = dataSource;  
  10.     }  
  11.     /* (non-Javadoc) 
  12.      * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[]) 
  13.      */  
  14.     @Override  
  15.     public Object invoke(Object proxy, Method method, Object[] args)  
  16.             throws Throwable {  
  17.         if(method.getName().equals("getConnection")){  
  18.               
  19.             if(ResourceHolder.getResource(proxy)!=null){  
  20.                 Connection connection = (Connection) method.invoke(this.dataSource, args);  
  21.                 ResourceHolder.addResource(proxy, connection);  
  22.             }  
  23.             return ResourceHolder.getResource(proxy);  
  24.         }else{  
  25.             return method.invoke(this.dataSource, args);  
  26.         }  
  27.     }  
  28. }  



         上面这个类是一个InvocationHandler的实现,假设这个就是Spring Aop拦截Datasource的实现。那么这个对象里面有一个datasource对象,这个对象是Spring替换代理Datasource之前的那个对象(datasource)。看到invoke的实现,其实就是将代理(datasourceproxy)调用的类发转到datasource去调用,其实还是调用了datasource,但是这里就加入了一些特殊的东西,那就是ResourceHolder

 

  1. <span style="font-size:18px">public class ResourceHolder {  
  2.   
  3.     private static ThreadLocal<Map<Object,Object>> resources= new ThreadLocal<Map<Object,Object>>();  
  4.       
  5.     public static void addResource(Object key,Object value){  
  6.         if(resources.get()==null){  
  7.             resources.set(new HashMap<Object,Object>());  
  8.         }  
  9.         resources.get().put(key, value);  
  10.     }  
  11.       
  12.     public static Object getResource(Object key){  
  13.           
  14.         return resources.get().get(key);  
  15.     }  
  16.       
  17.     public static void clear(){  
  18.         resources.remove();  
  19.     }  
  20. }</span>  

 上面是这个对象的实现,里面有一个ThreadLocal静态属性,用于存放一些数据信息。ThreadLocal用过的人都知道他是线程的局部变量,在整个线程过程中都是有效的。那么在invoke里面当每次调用的时候,判断调用的方法是不是getConnection,如果是,则进行如下操作

if(ResourceHolder.getResource(proxy)!=null){
Connection connection =(Connection) method.invoke(this.dataSource, args);
ResourceHolder.addResource(proxy, connection);
}

return ResourceHolder.getResource(proxy);
其中proxy就是Spring自动生成的datasourceproxy,将proxy和connection的关系添加到ResourceHolder里面去,而ResourceHolder又是将这个关系添加到ThreadLocal<Map<Object,Object>> resources这个静态变量里面,添加到这个里面,那么以后如果在当前线程从datasourceproxy获取connection对象,都将是一个对象,这就保证了一个业务方法里面进行多次dao操作,调用的都是一个connection对象,同时保证了多个dao都是在一个事务里面。既然这样,那么Spring的事务管理就可以在调用业务方法之前,先从datasource里面先获得一个connection对象,并且对connection添加上用户配置的事务规则,由于这个connection对象会自动添加到ThreadLocal里面,那么后面的业务处理方法将会是调用已经添加好事务规则的connection对象,当业务方法处理完毕,那么spring事务就可以对这个connection进行回滚或者提交了。经过这样一个过程,那么在一个处理某个业务的线程里面执行流程应该是这样的:

               

        总结一下:这里首选是对DataSource生成一个代理类,从而可以监控获取Connection的过程,在通过ThreadLocal对Connection线程级别的缓存从而促使在同一个业务处理方法相对于某个DataSource都是在一个Connection中,从而保证处于同一事务中,因为这些执行都是在一个线程中的。这里处理Spring的AOP之外,还有一个ThreadLocal的使用。在实践编程中,有时候你会发现ThreadLocal会带来很大的帮助。

        比如,你要在某个操作中的每个处理流程都要知道操作人信息,而且这个流程可能不是在一个方法或者一个类中处理完,如果在session环境中,你可能会考虑用session,但不是所有的开发都是在Session环境中的,那么此时ThreadLocal边是最好的帮手,可以在用户触发这个操作时候将用户信息放在ThreadLocal中,那么后面的每个流程都可以从ThreadLocal中获取,而且这个是线程范围的,每个线程中的ThreadLocal是不相干的,这样也防止了多线程的操作。

本文转载自:http://blog.csdn.net/jdream314/article/details/12647449

共有 人打赏支持
东小天
粉丝 0
博文 1
码字总数 0
作品 0
海淀
高级程序员
关于SpringBoot父子容器加载Bean的问题

Spring容器优先加载由ServletContextListener(对应applicationContext.xml)产生的父容器,而SpringMVC(对应mvcdispatcherservlet.xml)产生的是子容器。子容器Controller进行扫描装配时装...

孟德宣 ⋅ 05/19 ⋅ 0

spring注意事项

1.spring容器与springMVC容器冲突(spring version 4.0.2.RELEASE) SpringMVC容器是Spring容器的一个子容器,它同样能够初始化实体类。由于SpringMVC容器的初始化是在Spring容器初始化之后,...

图样图森破 ⋅ 2016/12/11 ⋅ 0

推荐几个自己写的Java后端相关的范例项目(转载)

http://wosyingjun.iteye.com/blog/2312553 这里推荐几个自己写的范例项目,主要采用SSM(Spring+SpringMVC+Mybatis)框架,分布式架构采用的是(dubbo+zookeeper)。范例项目的好处是简单易...

指尖的舞者 ⋅ 2016/09/27 ⋅ 0

SpringMVC+Spring事物失效问题

1、spring扫描配置 springmvc扫描配置导致事物失效 一个项目中既有SpringMVC又有Spring 的时候,会发生事物失效问题 原因:使用的spring注解+springMVC注解,默认情况下spring应该先加载appli...

宿小帅 ⋅ 2016/10/12 ⋅ 0

Spring AOP解释及在项目中使用举例

一.AOP是什么 AOP - Aspect Oriented Programing,面向切面编程。将封装好的对象切开,找出其中对多个对象产生影响的公共行为,并将其封装为一个可重用的模块,这个模块被命名为“切面”,切...

Jacktanger ⋅ 06/08 ⋅ 0

Spring AOP 日志拦截器的事务管理

如果要在方法执行前或后或抛出异常后加上一个自己的拦截器,或者一个环绕拦截器,在拦截器中执行一些操作,比如执行一些数据库操作,记录一些信 息,这些操作通过调用一个服务类的方法来执行...

哲别0 ⋅ 05/18 ⋅ 0

【问题解决】SSM-@Transactional注解事务无效探讨

Spring SpringMVC MyBatis框架中-@Transactional注解事务无效解决方法 SpringMVC-Spring-MyBatis框架 数据库:SQL SERVER 2008 以前都是用的MySQL数据库,没有出现这个问题,现在换成sql ser...

qq_26525215 ⋅ 2017/08/21 ⋅ 0

spring声明式事务@Transactional后置,前置处理

想必不少人遇到过这样子的场景,希望在spring的事务完成后do something... 前言: --------------------------------------------------------------------------------------------- 我遇到...

虾几把写 ⋅ 06/12 ⋅ 0

@Transaction必知必会

1. Spring事务的基本原理 事务管理是应用系统开发中必不可少的一部分。Spring 为事务管理提供了丰富的功能支持。Spring 事务管理分为编码式和声明式的两种方式。编程式事务指的是通过编码方式...

maskwang520 ⋅ 04/15 ⋅ 0

Spring探秘|妙用BeanPostProcessor

最近,在给项目组使用Spring搭建Java项目基础框架时,发现使用Spring提供的BeanPostProcessor可以很简单方便地解决很多看起来有点难解决的问题。本文将会通过一个真实案例来阐述BeanPostPro...

圆圆仙人球 ⋅ 2017/08/06 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

解决yum安装报错Protected multilib versions

使用yum安装报错Protected multilib versions原因是因为多个库不能共存,不过更新的话也并不行,但是可以在安装命令后面加上如下一段命令: --setopt=protected_multilib=false 案例: 比如需...

北岩 ⋅ 27分钟前 ⋅ 0

为什么要学习Typescript???

简单来说 目前的typescript就是未来的javascript 为什么?? 这要从ECMA-262标准的第4版说起 对了 我们说的ES5 其实是ECMAScript3.1这个替代性建议被扶正了而已... 那么 第4版标准是什么? 看看...

hang1989 ⋅ 32分钟前 ⋅ 0

linux安装ipfs

一、下载ipfs # cd /usr/local/ipfs/ # wget https://dist.ipfs.io/go-ipfs/v0.4.15/go-ipfs_v0.4.15_linux-amd64.tar.gz # tar -zxvf go-ipfs_v0.4.15_linux-amd64.tar.gz 二、安装ipfs # ......

八戒八戒八戒 ⋅ 37分钟前 ⋅ 0

jvm程序执行慢诊断手册

生产环境最多的几种事故之一就是程序执行慢,如果是web服务的话,表现就是响应时间长。本文分享,从业多年形成的排查守则。 诊断步骤 系统资源查看 首先是系统资源查看,而且必须是在第一步。...

xpbob ⋅ 38分钟前 ⋅ 0

YII2 advanced 高级版本项目搭建-添加API应用以及多应用

一、YII安裝 安裝yii可以用composer安裝,也可以在yii中文社区下载归档文件安装 composer安装就不介绍了,因为要安装composer,比较麻烦,当然安装了composer是最好的,以后安装yii的插件要用...

botkenni ⋅ 39分钟前 ⋅ 0

在jdk1.8的环境下模拟永久代内存溢出

相信不少小伙伴在看深入理解Java虚拟机的时候,作者给我们举例一个demo来发生PermGen space 1、通过List不断添加String.intern(); 2、通过设置对应的-XX:PermSize与-XX:MaxPermSize(更快看到...

虾几把写 ⋅ 今天 ⋅ 0

开发OpenDaylight组件的完整流程

在前面介绍学习了OpenDaylight的几个重要模块后,这里再来介绍下完整开发一个模块的过程。 OSGI的bundles提供被其他OSGI组件调用的服务。这个教程中展示的是Data Packet Service去解析数据包...

wangxuwei ⋅ 今天 ⋅ 0

Java序列化和反序列化

1、什么是序列化和反序列化 序列化:把对象转换为字节序列的过程。 反序列化:把字节序列恢复成对象的过程。 2、被序列化的类需要实现serializable接口,只是为了标注该对象是可以被序列化的...

IT-Mamba ⋅ 今天 ⋅ 0

流式构建原理

流式构建需要达到分钟级的数据更新频率,Kylin采用类似于Spark Streaming的做法,每隔数分钟进行一次微构建。这边的构建需要考虑到一个延迟因素,分布式网络存在延迟等因素,该时间段的数据有...

无精疯 ⋅ 今天 ⋅ 0

在maven项目工程编写solr代码,需要的依赖

solrJ <dependency> <groupId>org.apache.solr</groupId> <artifactId>solr-solrj</artifactId> <version>6.6.2</version> </dependency> <dependency> <groupId>org.apache.httpcomponents<......

爱运动的小乌龟 ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部