spring事务管理器设计思想(一)「建议收藏」

spring事务管理器设计思想(一)「建议收藏」spring事务管理器设计思想(一)

大家好,又见面了,我是你们的朋友全栈君。

在最近做的一个项目里面,涉及到多数据源的操作,比较特殊的是,这多个数据库的表结构完全相同,由于我们使用的ibatis框架作为持久化层,为了防止每一个数据源都配置一套规则,所以重新实现了数据源,根据线程变量中指定的数据库连接名称来获取实际的数据源。

一个简单的实现如下:

public class ProxyDataSource implements DataSource {
/** 数据源池配置 */
private Map<String, DataSource> dataSourcePoolConfig;

public Connection getConnection() throws SQLException {
        return createDataSource().getConnection();
}
private synchronized DataSource createDataSource() {
        String dbName = DataSourceContextHolder.getDbName();
        return dataSourcePoolConfig.get(dbName);
}

 

每次调用spring事务管理器之前,设置DataSourceContextHolder.set(“dbName”) 

事务提交之后在调用 DataSourceContextHolder.clear() 方法即可

 但是这样设计实际使用过程中也会遇到一些典型的问题,这就是在仔细了解spring中持久化层的设计之后,才能明白所产生的问题的原因。下面主要总结一下spring 持久化的设计。

 

Jdbc基本的编程模型

由于任何持久化层的封装实际上都是对java.sql.Connection等相关对象的操作,一个典型的数据操作的流程如下:

spring事务管理器设计思想(一)「建议收藏」

但在我们实际使用spring和ibatis的时候,都没有感觉到上面的流程,其实spring已经对外已经屏蔽了上述的操作,让我们更关注业务逻辑功能,但是我们有必要了解其实现,以便能够更好运用和定位问题。

 

开启事务:

在开启事务的时候,我们需要初始化事务上下文信息,以便在业务完成之后,需要知道事务的状态,以便进行后续的处理,这个上下文信息可以保存在 ThreadLocal里面,包括是否已经开启事务,事务的超时时间,隔离级别,传播级别,是否设置为回滚。这个信息对应用来说是透明的,但是提供给使用者编程接口,以便告知业务结束的时候是提交事务还是回滚事务。

 

获取连接

首先来看看spring如何获取数据库连接的,对于正常情况来看,获取连接直接调用DataSource.getConnection()就可以了,我们在自己实现的时候也肯定会这么做,但是需要考虑两种情况(这里面先不引入事务的传播属性):

1 还没有获取过连接,这是第一次获取连接

2 已经获取过连接,不是第一次获取连接,可以复用连接

解决获取数据库连接的关键问题就是如何判断是否已经可用的连接,而不需要开启新的数据库连接,同时由于数据库连接需要给后续的业务操作复用,如何保持这个连接,并且透明的传递给后续流程。对于一个简单的实现就是使用线程上下文变量ThrealLocal来解决以上两个问题。

具体的实现是:在获取数据库连接的时候,判断当前线程线程变量里面是否已经存在相关连接,如果不存在,就创新一个新的连接,如果存在,就直接获取其对应的连接。在第一次获取到数据库连接的时候,我们还需要做一些特殊处理,就是设置自动提交为false。在业务活动结束的时候在进行提交或者回滚。这个时候就是要调用connection.setAutoCommit(false)方法。

 spring事务管理器设计思想(一)「建议收藏」

执行sql

这一部分和业务逻辑相关,通过对外提供一些编程接口,可以让业务决定业务完成之后如何处理事务,比较简单的就是设置事务状态。

 

提交事务:

在开启事务的时候,事务上下文信息已经保存在线程变量里面了,可以根据事务上下文的信息,来决定是否是提交还是回滚。其实就是调用数据库连接Connection.commit 和 Connection.rollback 方法。然后需要清空线程变量中的事务上下文信息。相当于结束了当前的事务。

  

关闭连接:

关闭连接相对比较简单,由于当前线程变量保存了连接信息,只需要获取连接之后,调用connection.close方法即可,接着清空线程变量的数据库连接信息。

 上面几个流程是一个简单的事务处理流程,在spring中都有对应的实现,见TransactionTemplate.execute方法。Spring定义了一个TransactionSynchronizationManager对象,里面保存了各种线程变量信息,

 

//保存了数据源和其对应连接的映射,value是一个Map结构,其中key为datasource,value为其打开的连接

private static final ThreadLocal resources

//这个暂时用不到,不解释

private static final ThreadLocal synchronizations

//当前事务的名字

private static final ThreadLocal currentTransactionName

//是否是只读事务以及事务的隔离级别(这个一般我们都用不到,都是默认界别)

private static final ThreadLocal currentTransactionReadOnly

private static final ThreadLocal currentTransactionIsolationLevel

//代表是否是一个实际的事务活动,这个后面将)

private static final ThreadLocal actualTransactionActive

 

在获取连接的时候,可见DataSourceUtils.doGetConnection()方法,就是从调用TransactionSynchronizationManager.getResource(dataSource)获取连接信息,如果为空,就直接从调用dataSource.getConnection()创建新的连接,后面在调用

TransactionSynchronizationManager.bindResource(dataSource,conn)绑定数据源到线程变量,以便后续的线程在使用。

ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);

                   if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {

                            conHolder.requested();

                            if (!conHolder.hasConnection()) {

                                     logger.debug("Fetching resumed JDBC Connection from DataSource");

                                     conHolder.setConnection(dataSource.getConnection());

                            }

                            return conHolder.getConnection();

                   }

        

                   logger.debug("Fetching JDBC Connection from DataSource");

                   Connection con = dataSource.getConnection();

在提交事务的时候,见 DataSourceTransactionManager.doCommit方法,其实就是获取事务状态信息以及连接信息,调用conn.commmit方法,比较简单。

 

但是实际上,spring事务管理远远比上述复杂,我们没有考虑以下几种情况:

1 如果当前操作不需要事务支持,也就是每次执行一次,就自动进行提交。如何在同一个架构里面兼容这两种情况。比如就是简单的query操作。

2 一个业务活动跨越多个事务,每个事务的传播级别配置不一样。后面会拿一个例子来说明

 

对于第一个问题,比较好解决,首先就是根据线程变量里面获取数据源对应的连接,如果有连接,就复用。如果没有,就创建连接。在判断当前是否存在活动的事务上下文,如果存在事务信息,设置conn.setAutoCommit(false),然后设置线程上下文,绑定对应的数据源。如果不存在事务信息,就直接返回连接给应用。

这样就会带来一个新的问题,就是连接如何进行关闭。根据最开始的分析,在存在事务上下文的情况下,直接从获取线程获取对应的数据库连接,然后关闭。在关闭的也需要也进行判断一下即可。在spring里面,在事务中获取连接和关闭连接有一些特殊的处理,主要还是和其jdbc以及orm框架设计兼容。在jdbcTemplate,IbatiTemplate每执行一次sql操作,就需要获取conn,执行sql,关闭conn。如果不存在事务上下文,这样做没有任何问题,获取一次连接,使用完成,然后就是比。但是如果存在事务上下文,每次获取的conn并不一定是真实的物理连接,所以关闭的时候,也不能直接关闭这数据库连接。Spring的中定义一个ConnectionHandle对象,这个对象持有一个数据库连接对象,以及该连接上的引用次数(retain属性)。每次复用一次就retain++ 操作,没关闭一次,就执行retain– 操作,在retain 为0的时候,说明没有任何连接,就可以进行真实的关闭了。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/107083.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 【教程】详解VS2010安装流程[通俗易懂]

    【教程】详解VS2010安装流程  原文网址链接为:http://jingyan.baidu.com/article/4e5b3e195b838991901e24e5.html  VS2010全称“Microsoft Visual Studio 2010”,是微软公司推出的开发环境。也是目前流行的Windows平台应用程序开发环境。注意:在第6步安装时,产品安装路径可以选择

    2022年4月16日
    59
  • 毕业设计——基于小程序云开发的校园二手交易平台(附源码)

    毕业设计——基于小程序云开发的校园二手交易平台(附源码)本系统基于微信小程序云开发,采用小程序原生框架,不需要后端开发,数据库和CMS云开发全帮你搞定,对后端开发能力薄弱的同学超友好的有木有,只要你学过HTML+CSS+JS就能实现所有功能。一、功能介绍使用该系统的角色有两个,分别是用户和管理员,用户通过微信小程序进入该系统,该角色功能主要由七个模块组成,分别是用户登录、搜索商品、发布商品、管理商品、购买商品、收藏商品、以及评论留言。管理员通过小程序自带的内容管理平台实现后台管理,可对商品信息、留言板以及用户等数据进行增、删、改、查操作。二、业务流程图

    2022年6月19日
    25
  • TP5与TP3.X对比

    TP5与TP3.X对比

    2021年10月12日
    39
  • java parrallel for,Java 8 parallel forEach进度指示

    java parrallel for,Java 8 parallel forEach进度指示ForperformancereasonIwouldliketouseaforEachloopofaparallelLambdastreaminordertoprocessaninstanceofaCollectioninJava.AsthisrunsinabackgroundServiceIwouldliketouse…

    2022年7月19日
    18
  • APP抓包工具Fidder

    APP抓包工具FidderAPP抓包工具Fidder0.安装下载Classic版本https://www.telerik.com/download/fiddler1.使用a.设置Fiddler打开Fiddler,Tools->FiddlerOptions(配置完后记得要重启Fiddler)选中”DecrptHTTPStraffic”,Fiddler就可以截获HTTPS请求选中”Allowremotecomputerstoconnect”.是允许别的机器把HTTP/HTTPS请求

    2022年5月22日
    53
  • SQL中的模糊查询like「建议收藏」

    SQL中的模糊查询like「建议收藏」首先我们创建一个Person表。createtablePerson(cnamevarchar2(50),cagenumber(3));插入一些数据:insertintoPerson(cname,cage)values(‘张三’,19);insertintoPerson(cname,cage)values(‘张三丰’,20);insertintoPerson(cname,cage)valu…

    2022年6月7日
    30

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号