【原创】Spring连接、事务代码分析

摘要:
conHolder.hasConnection()){logger.debug;conHolder.setConnection;}returnconHolder.getConnection();//首先从当前的线程上下文中获取}//Elseweeithergotnoholderoranemptythread-boundholderhere.logger.debug;Connectioncon=dataSource.getConnection();//若线程上下文中未找到,则新建一个连接当执行完查询,将数据抽取封装好之后,最后将Statement和connection关闭:publicObjectexecutethrowsDataAccessException{Assert.notNull;Connectioncon=DataSourceUtils.getConnection;Statementstmt=null;try{ConnectionconToUse=con;if(this.nativeJdbcExtractor!=null){stmtToUse=this.nativeJdbcExtractor.getNativeStatement;}Objectresult=action.doInStatement;SQLWarningwarning=stmt.getWarnings();throwExceptionOnWarningIfNotIgnoringWarnings;returnresult;//返回语句执行的结果}catch{//ReleaseConnectionearly,toavoidpotentialconnectionpooldeadlock//inthecasewhentheexceptiontranslatorhasn'tbeeninitializedyet.JdbcUtils.closeStatement;stmt=null;DataSourceUtils.releaseConnection;con=null;throwgetExceptionTranslator().translate;}finally{JdbcUtils.closeStatement;//最后释放资源关闭连接DataSourceUtils.releaseConnection;}}可见,在不使用事务时,每执行一个语句都是一个单独的事务,和原来不使用spring时是一样的,完毕之后spring会自动释放所有资源和关闭连接。connectionholder字面意思为连接持有者,即为每个线程保存绑定连接的对象,每个线程绑定的连接保存在该对象中,然后保存在一个

1.JdbcTemplate

当不使用事务时,jdbcTemplate的模板类,通过
Connection con = DataSourceUtils.getConnection(getDataSource());
方法,首先去当前线程的上下文中寻找绑定的数据库连接,若没找到,则新建一个连接,即从DataSource中创建一个新的连接:
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();//首先从当前的线程上下文中获取
}
        //Else we either got no holder or an empty thread-bound holder here.
logger.debug("Fetching JDBC Connection from DataSource");
Connection con = dataSource.getConnection();//若线程上下文中未找到,则新建一个连接
当执行完查询,将数据抽取封装好之后,最后将Statement和connection关闭:
public Object execute(StatementCallback action) throwsDataAccessException {
        Assert.notNull(action, "Callback object must not be null");

        Connection con =DataSourceUtils.getConnection(getDataSource());
        Statement stmt = null;
        try{
            Connection conToUse =con;
            if (this.nativeJdbcExtractor != null &&
                    this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()) {
                conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
            }
            stmt =conToUse.createStatement();
            applyStatementSettings(stmt);
            Statement stmtToUse =stmt;
            if (this.nativeJdbcExtractor != null) {
                stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt);
            }
            Object result =action.doInStatement(stmtToUse);
            SQLWarning warning =stmt.getWarnings();
            throwExceptionOnWarningIfNotIgnoringWarnings(warning);
            return result;//返回语句执行的结果
}
        catch(SQLException ex) {
            //Release Connection early, to avoid potential connection pool deadlock
            //in the case when the exception translator hasn't been initialized yet.
JdbcUtils.closeStatement(stmt);
            stmt = null;
            DataSourceUtils.releaseConnection(con, getDataSource());
            con = null;
            throw getExceptionTranslator().translate("StatementCallback", getSql(action), ex);
        }
        finally{
            JdbcUtils.closeStatement(stmt);//最后释放资源关闭连接
DataSourceUtils.releaseConnection(con, getDataSource());
        }
    }
可见,在不使用事务时,每执行一个语句都是一个单独的事务,和原来不使用spring时是一样的,完毕之后spring会自动释放所有资源和关闭连接。
2.如果使用事务呢?
首先需要定义一个
<bean   class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
<property name="dataSource" ref="datasource"/>

然后定义一个:DefaultTransactionDefinition

DefaultTransactionDefinition def = new DefaultTransactionDefinition();    def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

由此创建一个TransactionStatus:

    privateTransactionStatus beginTransaction(){
        DefaultTransactionDefinition def = newDefaultTransactionDefinition();
        def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        returngetMager().getTransaction(def);
    }
    privateDataSourceTransactionManager getMager(){
         DataSourceTransactionManager man=   (DataSourceTransactionManager)                                  con.getBean("transactionManager");
         returnman;
    }

下面看创建status时做了什么?

connectionholder字面意思为连接持有者,即为每个线程保存绑定连接的对象,每个线程绑定的连接保存在该对象中,然后保存在一个map里面,key为当前的DataSource,该DataSource为应用程序级的,可以是一个tns连接描述,也可以是一个jndi也可以是一个连接池对象,总之所有线程共享,通过该key值找到线程自己通过该DateSource创建的连接,该Map保存到线程自己的本地变量中,以便下次获取,下次线程获取连接时,首先去自己的本地变量中寻找map,看map释放为空,如果为空,说明没有绑定的连接,直接创建,若有,则通过jdbcTemplate中的DataSource对象作为key去map中拿出绑定的连接使用。

protected voiddoBegin(Object transaction, TransactionDefinition definition) {
        DataSourceTransactionObject txObject =(DataSourceTransactionObject) transaction;

        Connection con = null;

        try{
            if (txObject.getConnectionHolder() == null) {//如果当前的connectionholder不存在
                Connection newCon = this.dataSource.getConnection();//则创建一个新连接
                if(logger.isDebugEnabled()) {
                    logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
                }
                txObject.setConnectionHolder(new ConnectionHolder(newCon), true);//并将该连接进行持有
}

            txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
            con =txObject.getConnectionHolder().getConnection();

            Integer previousIsolationLevel =DataSourceUtils.prepareConnectionForTransaction(con, definition);
            txObject.setPreviousIsolationLevel(previousIsolationLevel);

            //Switch to manual commit if necessary. This is very expensive in some JDBC drivers,
            //so we don't want to do it unnecessarily (for example if we've explicitly
            //configured the connection pool to set it already).
            if(con.getAutoCommit()) {
                txObject.setMustRestoreAutoCommit(true);
                if(logger.isDebugEnabled()) {
                    logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
                }
                con.setAutoCommit(false);//为事务做准备
}
            txObject.getConnectionHolder().setTransactionActive(true);

            if (definition.getTimeout() !=TransactionDefinition.TIMEOUT_DEFAULT) {
                txObject.getConnectionHolder().setTimeoutInSeconds(definition.getTimeout());
            }

            //Bind the session holder to the thread.
            if(txObject.isNewConnectionHolder()) {
                TransactionSynchronizationManager.bindResource(getDataSource(), txObject.getConnectionHolder());
            }//重要的一步,将该连接和当前的线程进行绑定,并将该连接的自动提交设为false,为事务使用做准备
}

        catch (SQLException ex) {//出现异常则关闭任何打开的连接,物理释放
            DataSourceUtils.releaseConnection(con, this.dataSource);
            throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
        }
    }

这时,status其实就是将连接和当前线程绑定,然后做一些必要的准备工作,如事务传播级别等等的设置:

public static void bindResource(Object key, Object value) throwsIllegalStateException {
        Assert.notNull(key, "Key must not be null");
        Assert.notNull(value, "Value must not be null");
        Map map = (Map) resources.get();//从ThreadLocal中获取当前连接信息的Map结构
        //set ThreadLocal Map if none found
        if (map == null) {
            map = newHashMap();
            resources.set(map);//如果未找到,则将其设置进去
}
        if(map.containsKey(key)) {
            throw new IllegalStateException("Already value [" + map.get(key) + "] for key [" + key +
                    "] bound to thread [" + Thread.currentThread().getName() + "]");
        }
        map.put(key, value);
        if(logger.isDebugEnabled()) {
            logger.debug("Bound value [" + value + "] for key [" + key + "] to thread [" +Thread.currentThread().getName() + "]");
        }
    }

如果想获得当前会话中执行的数据库连接,则使用以下方法:

Connection conn = DataSourceUtils.getConnection(dataSource);
这个获得和当前事务绑定的连接,否则使用getDataSource().getConnection()会创建一个全新的连接,这样会造成出现事务从而被隔离了。

当执行了getTransaction方法并返回一个TransactionStatus之后,表示事务已经开启了,后续所有的执行语句的操作都会使用一个数据库连接,而该连接在步骤1中会自动从当前线程的上下文中获取:

    public staticObject getResource(Object key) {
        Assert.notNull(key, "Key must not be null");
        Map map = (Map) resources.get();//从当前线程的上下文中拿到线程保持的map,为什么不直接将连接保持到resources中,而是放入一个map再放入resource中呢?原因是,类TransactionSynchronizationManager为一个事务同步的管理器,不光是为了持有连接,还有其他资源,所以,将线程自身其他的一些资源放入map,可以保寸多种对象不至于和其他线程产生冲突
        if (map == null) {
            return null;
        }
        Object value = map.get(key);//如果是想拿到连接,此时的key是一个DataSource对象,说明是想从线程的变量集合中拿到本线程绑定的数据库连接
        if (value != null &&logger.isDebugEnabled()) {
            logger.debug("Retrieved value [" + value + "] for key [" + key + "] bound to thread [" +Thread.currentThread().getName() + "]");
        }
        returnvalue;
    }

未来后续的所有的操作使用的连接都将是该连接,而不是建立新的连接,这和不使用事务时有根本的区别。而事务开始之后,每个语句执行完毕之后,finally语句都会关闭连接释放资源,那么此时将会如何执行呢?

public static void doReleaseConnection(Connection con, DataSource dataSource) throwsSQLException {
        if (con == null) {
            return;
        }

        if (dataSource != null) {
            ConnectionHolder conHolder =(ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
            if (conHolder != null && conHolder.hasConnection() && connectionEquals(conHolder.getConnection(), con)) {//在status创建时初始化的holder对象在这个地方区分出到底是事务连接还是普通的非事务连接,如果是事务连接,那么执行Holder自己的释放方法
                //It's the transactional Connection: Don't close it.
conHolder.released();
                return;
            }
        }

        //Leave the Connection open only if the DataSource is our
        //special data source, and it wants the Connection left open.
        if (!(dataSource instanceof SmartDataSource) ||((SmartDataSource) dataSource).shouldClose(con)) {
            logger.debug("Returning JDBC Connection to DataSource");
            con.close();//这是不使用事务时使用的释放方法,即为物理关闭连接,执行完后,该连接即实实在在的关闭
}
    }

那么conHolder.released();如何执行呢?

public voidreleased() {
        this.referenceCount--;
        if (this.currentConnection != null) {
            this.connectionHandle.releaseConnection(this.currentConnection);//这是一个空方法
            this.currentConnection = null;//释放引用
}
    }
可见,此处释放连接并没有真正的关闭连接,而是将指向ThreadLocal中和当前线程绑定的连接的引用释放,因为连接和当前线程绑定,只要当前线程没用执行完,堆栈未被回收释放,则该连接会一直存在,事务中其他语句执行时使用的连接都是该连接,所以需要有一个到当前连接的引用,语句执行完后finally中释放的连接即是该引用而已。连接为被真正释放,什么时候连接真正关闭呢?
在Commit之后,在TransactionManager的doCleanupAfterCompletion(Object transaction) 方法中:
protected voiddoCleanupAfterCompletion(Object transaction) {
        DataSourceTransactionObject txObject =(DataSourceTransactionObject) transaction;

        //Remove the connection holder from the thread, if exposed.
        if(txObject.isNewConnectionHolder()) {
            TransactionSynchronizationManager.unbindResource(this.dataSource);//解除连接绑定
}

        //Reset connection.
        Connection con =txObject.getConnectionHolder().getConnection();
        try{
            if(txObject.isMustRestoreAutoCommit()) {
                con.setAutoCommit(true);//虽然将要关闭连接,但是仍然将该连接的自动提交恢复,为什么呢?原因就是,该连接可能是从连接池中拿到的,从连接池中拿到的连接并不会真正关闭,而是返回连接池,因此需要将该连接重置初始化,否则该连接被其他线程拿到时会影响执行结果,如无法自动提交,丢失事务等。可见spring想的非常周到。而且后续会将该连接一系列的属性重置,如事务的隔离级别、只读事务等等
}
            DataSourceUtils.resetConnectionAfterTransaction(con, txObject.getPreviousIsolationLevel());
//重置连接的一些属性,以为了连接重用
}
        catch(Throwable ex) {
            logger.debug("Could not reset JDBC Connection after transaction", ex);
        }

        if(txObject.isNewConnectionHolder()) {
            if(logger.isDebugEnabled()) {
                logger.debug("Releasing JDBC Connection [" + con + "] after transaction");
            }
            DataSourceUtils.releaseConnection(con, this.dataSource);//最后会释放连接,由于连接已经和当前线程解除绑定,因此关闭连接是物理关闭(此处关闭的方法由数据库的驱动程序决定,驱动程序关闭时会判断是普通连接还是连接池连接,对应用程序是透明的)
}

        txObject.getConnectionHolder().clear();//清除Holder信息,将当前为事务做的准备工作和信息全部清除归位
    }

免责声明:文章转载自《【原创】Spring连接、事务代码分析》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇轻量级ORMPetaPoco及改进Linux普通用户执行提示权限不够,sudo提示找不到命令下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

多线程:C#线程同步lock,Monitor,Mutex,同步事件和等待句柄

转自:http://www.cnblogs.com/freshman0216/archive/2008/07/29/1252253.html 本篇从Monitor,Mutex,ManualResetEvent,AutoResetEvent,WaitHandler的类关系图开始, 希望通过本篇的介绍能对常见的线程同步方法有一个整体的认识,而对每种方式的使用细...

多线程和CPU的关系

什么是CPU (1)         Central  Progressing  Unit 中央处理器,是一块超大规模的集成电路,是一台计算机的运算核心和控制核心。 (2)         CPU包括 运算器,高速缓冲存储器,总线。 (3)         它的工作,主要是解释计算机中的指令,和处理计算机软件中的数据。它在计算机中起着最重要的作用,构成了系...

@Transactional注解失效

一、特性 先来了解一下@Transactional注解事务的特性吧,可以更好排查问题 1、service类标签(一般不建议在接口上)上添加@Transactional,可以将整个类纳入spring事务管理,在每个业务方法执行时都会开启一个事务,不过这些事务采用相同的管理方式。 2、@Transactional 注解只能应用到 public 可见度的方法上。...

JVM内存越多,能创建的线程越少,越容易发生java.lang.OutOfMemoryError: unable to create new native thread。

一、认识问题: 首先我们通过下面这个 测试程序 来认识这个问题:运行的环境 (有必要说明一下,不同环境会有不同的结果):32位 Windows XP,Sun JDK 1.6.0_18, eclipse 3.4,测试程序: Java代码   import java.util.concurrent.CountDownLatch;       public...

delphi 利用 InterlockedCompareExchange 实现主线程维一锁等待

在进行资源锁定时,一般是线程之间进行交互,很少需要在主线程也对资源进行锁定。    不过在一些复杂的业务中,存在子线程与主线程的交互,且一些资源也同步在主线程中使用时,主线程资源锁,就有存在的必要。 假定有一个需求,在SQLITE更新时,需要共同一个更新组件,以减少资源建立与释放及相对应内存回收的需求,则此时的每一个更新,就有先锁定再更新的必要。 废话不多...

apache安装时的一些术语

apache源码安装时,需要的哪些必须依赖模块? 主要需要apr, apr-util, pcre模块其中 apr模块时必须的. 如何卸载 源码安装的软件? 在源码 的 解压目录下, 使用 make uninstall/ make clean, make distclean 直接将 安装在--prefix目录下的文件全部都删除掉即可! 使用 --prefi...