深入分析Spring混合事务

 

在ORM框架的事务管理器的事务内,使用JdbcTemplate执行SQL是不会纳入事务管理的。

下面进行源码分析,看为什么必须要在DataSourceTransactionManager的事务内使用JdbcTemplate。


1开启事务

DataSourceTransactionManager

===============================================================================

         protected void doBegin(Object transaction,TransactionDefinition definition) {
                   DataSourceTransactionObjecttxObject = (DataSourceTransactionObject) transaction;
                   Connection con = null;
 
                   try {
                            if(txObject.getConnectionHolder() == null ||
                                               txObject.getConnectionHolder().isSynchronizedWithTransaction()){
                                     ConnectionnewCon = this.dataSource.getConnection();
                                     if(logger.isDebugEnabled()) {
                                               logger.debug("AcquiredConnection [" + newCon + "] for JDBC transaction");
                                     }
                                     txObject.setConnectionHolder(newConnectionHolder(newCon), true);
                            }
 
                            txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
                            con =txObject.getConnectionHolder().getConnection();
 
                            IntegerpreviousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con,definition);
                            txObject.setPreviousIsolationLevel(previousIsolationLevel);
 
                            // Switch to manualcommit if necessary. This is very expensive in some JDBC drivers,
                            // so we don't wantto do it unnecessarily (for example if we've explicitly
                            // configured theconnection pool to set it already).
                            if(con.getAutoCommit()) {
                                     txObject.setMustRestoreAutoCommit(true);
                                     if(logger.isDebugEnabled()) {
                                               logger.debug("SwitchingJDBC Connection [" + con + "] to manual commit");
                                     }
                                     con.setAutoCommit(false);
                            }
                            txObject.getConnectionHolder().setTransactionActive(true);
 
                            int timeout =determineTimeout(definition);
                            if (timeout !=TransactionDefinition.TIMEOUT_DEFAULT) {
                                     txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
                            }
 
                            // Bind the sessionholder to the thread.
                            if(txObject.isNewConnectionHolder()) {
                                     TransactionSynchronizationManager.bindResource(getDataSource(),txObject.getConnectionHolder());
                            }
                   }
 
                   catch (Exception ex) {
                            DataSourceUtils.releaseConnection(con,this.dataSource);
                            throw newCannotCreateTransactionException("Could not open JDBC Connection fortransaction", ex);
                   }
         }

doBegin()方法会以数据源名为Key,ConnectionHolder(保存着连接)为Value,将已经开启事务的数据库连接绑定到一个ThreadLocal变量上。


2绑定连接

TransactionSynchronizationManager

===============================================================================

         public static void bindResource(Objectkey, Object value) throws IllegalStateException {
                   Object actualKey =TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
                   Assert.notNull(value,"Value must not be null");
                   Map<Object, Object> map = resources.get();
                   // set ThreadLocal Map ifnone found
                   if (map == null) {
                            map = newHashMap<Object, Object>();
                            resources.set(map);
                   }
                   Object oldValue = map.put(actualKey, value);
                   // Transparently suppress aResourceHolder that was marked as void...
                   if (oldValue instanceofResourceHolder && ((ResourceHolder) oldValue).isVoid()) {
                            oldValue = null;
                   }
                   if (oldValue != null) {
                            throw newIllegalStateException("Already value [" + oldValue + "] for key[" +
                                               actualKey+ "] bound to thread [" + Thread.currentThread().getName() +"]");
                   }
                   if (logger.isTraceEnabled()){
                            logger.trace("Boundvalue [" + value + "] for key [" + actualKey + "] to thread[" +
                                               Thread.currentThread().getName()+ "]");
                   }
         }

resources变量就是上面提到的ThreadLocal变量,这样后续JdbcTemplate就可以用DataSource作为Key,查找到这个数据库连接。


3执行SQL

JdbcTemplate

===============================================================================

         public Objectexecute(PreparedStatementCreator psc, PreparedStatementCallback action)
                            throwsDataAccessException {
 
                   Assert.notNull(psc,"PreparedStatementCreator must not be null");
                   Assert.notNull(action,"Callback object must not be null");
                   if (logger.isDebugEnabled()){
                            String sql =getSql(psc);
                            logger.debug("Executingprepared SQL statement" + (sql != null ? " [" + sql +"]" : ""));
                   }
 
                   Connection con = DataSourceUtils.getConnection(getDataSource());
                   PreparedStatement ps = null;
                   try {
                            Connection conToUse= con;
                            if(this.nativeJdbcExtractor != null &&
                                               this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativePreparedStatements()){
                                     conToUse =this.nativeJdbcExtractor.getNativeConnection(con);
                            }
                            ps =psc.createPreparedStatement(conToUse);
                            applyStatementSettings(ps);
                            PreparedStatementpsToUse = ps;
                            if(this.nativeJdbcExtractor != null) {
                                     psToUse =this.nativeJdbcExtractor.getNativePreparedStatement(ps);
                            }
                            Object result =action.doInPreparedStatement(psToUse);
                            handleWarnings(ps);
                            return result;
                   }
                   catch (SQLException ex) {
                            // ReleaseConnection early, to avoid potential connection pool deadlock
                            // in the case whenthe exception translator hasn't been initialized yet.
                            if (psc instanceofParameterDisposer) {
                                     ((ParameterDisposer)psc).cleanupParameters();
                            }
                            String sql =getSql(psc);
                            psc = null;
                            JdbcUtils.closeStatement(ps);
                            ps = null;
                            DataSourceUtils.releaseConnection(con,getDataSource());
                            con = null;
                            throwgetExceptionTranslator().translate("PreparedStatementCallback", sql,ex);
                   }
                   finally {
                            if (psc instanceofParameterDisposer) {
                                     ((ParameterDisposer)psc).cleanupParameters();
                            }
                            JdbcUtils.closeStatement(ps);
                            DataSourceUtils.releaseConnection(con,getDataSource());
                   }
         }

 

4获得连接

DataSourceUtils

===============================================================================

        public static Connection doGetConnection(DataSourcedataSource) throws SQLException {
                   Assert.notNull(dataSource,"No DataSource specified");
 
                   ConnectionHolder conHolder = (ConnectionHolder)TransactionSynchronizationManager.getResource(dataSource);
                   if (conHolder != null&& (conHolder.hasConnection() ||conHolder.isSynchronizedWithTransaction())) {
                            conHolder.requested();
                            if(!conHolder.hasConnection()) {
                                     logger.debug("Fetchingresumed JDBC Connection from DataSource");
                                     conHolder.setConnection(dataSource.getConnection());
                            }
                            returnconHolder.getConnection();
                   }
                   // Else we either got noholder or an empty thread-bound holder here.
 
                   logger.debug("FetchingJDBC Connection from DataSource");
                   Connection con =dataSource.getConnection();
 
                   if (TransactionSynchronizationManager.isSynchronizationActive()){
                            logger.debug("Registeringtransaction synchronization for JDBC Connection");
                            // Use sameConnection for further JDBC actions within the transaction.
                            // Thread-boundobject will get removed by synchronization at transaction completion.
                            ConnectionHolderholderToUse = conHolder;
                            if (holderToUse ==null) {
                                     holderToUse= new ConnectionHolder(con);
                            }
                            else {
                                     holderToUse.setConnection(con);
                            }
                            holderToUse.requested();
                            TransactionSynchronizationManager.registerSynchronization(
                                               newConnectionSynchronization(holderToUse, dataSource));
                            holderToUse.setSynchronizedWithTransaction(true);
                            if (holderToUse !=conHolder) {
                                     TransactionSynchronizationManager.bindResource(dataSource,holderToUse);
                            }
                   }
 
                   return con;
         }

 

由此可见,DataSourceUtils也是通过TransactionSynchronizationManager获得连接的。所以只要JdbcTemplate与DataSourceTransactionManager有相同的DataSource,就一定能得到相同的数据库连接,自然就能正确地提交、回滚事务。

 

再以Hibernate为例来说明开篇提到的问题,看看为什么ORM框架的事务管理器不能管理JdbcTemplate。


5 ORM事务管理器

HibernateTransactionManager

===============================================================================

                            if(txObject.isNewSessionHolder()) {
                                     TransactionSynchronizationManager.bindResource(getSessionFactory(),txObject.getSessionHolder());
                            }

因为ORM框架都不是直接将DataSource注入到TransactionManager中使用的,而是像上面Hibernate事务管理器一样,使用自己的SessionFactory等对象来操作DataSource。所以尽管可能SessionFactory和JdbcTemplate底层都是一样的数据源,但因为在TransactionSynchronizationManager中绑定时使用了不同的Key(一个是sessionFactory名,一个是dataSource名),所以JdbcTemplate执行时是拿不到ORM事务管理器开启事务的那个数据库连接的。

 

posted on 2013-09-14 21:04  毛小娃  阅读(365)  评论(0编辑  收藏  举报

导航