Rising Sun

            BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
            148 隨筆 :: 0 文章 :: 22 評論 :: 0 Trackbacks

          Spring的事務(wù)管理難點(diǎn)剖析(7):數(shù)據(jù)連接泄漏
          底層連接資源的訪問問題 

             對于應(yīng)用開發(fā)者來說,數(shù)據(jù)連接泄漏無疑是一個可怕的夢魘。只要你開發(fā)的應(yīng)用存在數(shù)據(jù)連接泄漏的問題,應(yīng)用程序最終都將因數(shù)據(jù)連接資源的耗盡而崩潰,甚至還可能引起數(shù)據(jù)庫的崩潰。數(shù)據(jù)連接泄漏像一個黑洞那樣讓開發(fā)者避之唯恐不及。 
             Spring DAO對所有支持的數(shù)據(jù)訪問技術(shù)框架都使用模板化技術(shù)進(jìn)行了薄層的封裝。只要你的程序都使用Spring DAO的模板(如JdbcTemplate、HibernateTemplate等)進(jìn)行數(shù)據(jù)訪問,一定不會存在數(shù)據(jù)連接泄漏的問題——這是Spring給予我們的鄭重承諾!如果使用Spring DAO模板進(jìn)行數(shù)據(jù)操作,我們無須關(guān)注數(shù)據(jù)連接(Connection)及其衍生品(Hibernate的Session等)的獲取和釋放操作,模板類已經(jīng)通過其內(nèi)部流程替我們完成了,且對開發(fā)者是透明的。 
             但是由于集成第三方產(chǎn)品、整合遺產(chǎn)代碼等原因,可能需要直接訪問數(shù)據(jù)源或直接獲取數(shù)據(jù)連接及其衍生品。這時,如果使用不當(dāng),就可能在無意中創(chuàng)造出一個魔鬼般的連接泄漏問題。 
          我們知道:當(dāng)Spring事務(wù)方法運(yùn)行時,就產(chǎn)生一個事務(wù)上下文,該上下文在本事務(wù)執(zhí)行線程中針對同一個數(shù)據(jù)源綁定了一個唯一的數(shù)據(jù)連接(或其衍生品),所有被該事務(wù)上下文傳播的方法都共享這個數(shù)據(jù)連接。這個數(shù)據(jù)連接從數(shù)據(jù)源獲取及返回給數(shù)據(jù)源都在Spring掌控之中,不會發(fā)生問題。如果在需要數(shù)據(jù)連接時,能夠獲取這個被Spring管控的數(shù)據(jù)連接,則使用者可以放心使用,無須關(guān)注連接釋放的問題。 
              那么,如何獲取這些被Spring管控的數(shù)據(jù)連接呢?Spring提供了兩種方法:其一是使用數(shù)據(jù)資源獲取工具類;其二是對數(shù)據(jù)源(或其衍生品如Hibernate SessionFactory)進(jìn)行代理。 

          Spring JDBC數(shù)據(jù)連接泄漏 

             如果我們從數(shù)據(jù)源直接獲取連接,且在使用完成后不主動歸還給數(shù)據(jù)源(調(diào)用Connection#close()),則將造成數(shù)據(jù)連接泄漏的問題。 
          1. package com.baobaotao.connleak;  
          2. …  
          3. @Service("jdbcUserService")  
          4. public class JdbcUserService {  
          5.     @Autowired  
          6.     private JdbcTemplate jdbcTemplate;  
          7.   
          8.     @Transactional  
          9.     public void logon(String userName) {  
          10.         try {  
          11.   
          12.             //①直接從數(shù)據(jù)源獲取連接,后續(xù)程序沒有顯式釋放該連接  
          13.             Connection conn = jdbcTemplate.getDataSource().getConnection();  
          14.             String sql = "UPDATE t_user SET last_logon_time=? WHERE user_name =?";  
          15.             jdbcTemplate.update(sql, System.currentTimeMillis(), userName);  
          16.   
          17.             //②模擬程序代碼的執(zhí)行時間  
          18.             Thread.sleep(1000);   
          19.         } catch (Exception e) {  
          20.             e.printStackTrace();  
          21.         }  
          22.     }  
          23. }  

             JdbcUserService通過Spring AOP事務(wù)增強(qiáng)的配置,讓所有public方法都工作在事務(wù)環(huán)境中,即讓logon()和updateLastLogonTime()方法擁有事務(wù)功能。在logon()方法內(nèi)部,我們在①處通過調(diào)用jdbcTemplate.getDataSource().getConnection()顯式獲取一個連接,這個連接不是logon()方法事務(wù)上下文線程綁定的連接,所以如果開發(fā)者沒有手工釋放這個連接(顯式調(diào)用Connection#close()方法),則這個連接將永久被占用(處于active狀態(tài)),造成連接泄漏!下面,我們編寫模擬運(yùn)行的代碼,查看方法執(zhí)行對數(shù)據(jù)連接的實(shí)際占用情況: 
          Java代碼  收藏代碼
          1. package com.baobaotao.connleak;  
          2. …  
          3. @Service("jdbcUserService")  
          4. public class JdbcUserService {  
          5.     …  
          6.     //①以異步線程的方式執(zhí)行JdbcUserService#logon()方法,以模擬多線程的環(huán)境  
          7.     public static void asynchrLogon(JdbcUserService userService, String userName) {  
          8.         UserServiceRunner runner = new UserServiceRunner(userService, userName);  
          9.         runner.start();  
          10.     }  
          11.     private static class UserServiceRunner extends Thread {  
          12.         private JdbcUserService userService;  
          13.         private String userName;  
          14.         public UserServiceRunner(JdbcUserService userService, String userName) {  
          15.             this.userService = userService;  
          16.             this.userName = userName;  
          17.         }  
          18.         public void run() {  
          19.             userService.logon(userName);  
          20.         }  
          21.     }  
          22.       
          23.    //②讓主執(zhí)行線程睡眠一段指定的時間  
          24.    public static void sleep(long time) {  
          25.         try {  
          26.             Thread.sleep(time);  
          27.         } catch (InterruptedException e) {  
          28.             e.printStackTrace();  
          29.         }  
          30.     }  
          31.   
          32.    //③匯報(bào)數(shù)據(jù)源的連接占用情況  
          33.     public static void reportConn(BasicDataSource basicDataSource) {  
          34.         System.out.println("連接數(shù)[active:idle]-[" +  
          35.                        basicDataSource.getNumActive()+":"+basicDataSource.getNumIdle()+"]");  
          36.     }  
          37.   
          38.     public static void main(String[] args) {  
          39.         ApplicationContext ctx =   
          40.          new ClassPathXmlApplicationContext("com/baobaotao/connleak/applicatonContext.xml");  
          41.         JdbcUserService userService = (JdbcUserService) ctx.getBean("jdbcUserService");  
          42.   
          43.         BasicDataSource basicDataSource = (BasicDataSource) ctx.getBean("dataSource");  
          44.           
          45.         //④匯報(bào)數(shù)據(jù)源初始連接占用情況  
          46.         JdbcUserService.reportConn(basicDataSource);  
          47.   
          48.         JdbcUserService.asynchrLogon(userService, "tom");//啟動一個異常線程A  
          49.         JdbcUserService.sleep(500);  
          50.   
          51.         //⑤此時線程A正在執(zhí)行JdbcUserService#logon()方法  
          52.         JdbcUserService.reportConn(basicDataSource);   
          53.   
          54.         JdbcUserService.sleep(2000);  
          55.   
          56.         //⑥此時線程A所執(zhí)行的JdbcUserService#logon()方法已經(jīng)執(zhí)行完畢  
          57.         JdbcUserService.reportConn(basicDataSource);  
          58.   
          59.         JdbcUserService.asynchrLogon(userService, "john");//啟動一個異常線程B  
          60.         JdbcUserService.sleep(500);  
          61.   
          62.         //⑦此時線程B正在執(zhí)行JdbcUserService#logon()方法  
          63.         JdbcUserService.reportConn(basicDataSource);  
          64.           
          65.         JdbcUserService.sleep(2000);  
          66.   
          67.         //⑧此時線程A和B都已完成JdbcUserService#logon()方法的執(zhí)行  
          68.         JdbcUserService.reportConn(basicDataSource);  
          69.   
          70.     }  


              在JdbcUserService中添加一個可異步執(zhí)行l(wèi)ogon()方法的asynchrLogon()方法,我們通過異步執(zhí)行l(wèi)ogon()以及讓主線程睡眠的方式模擬多線程環(huán)境下的執(zhí)行場景。在不同的執(zhí)行點(diǎn),通過reportConn()方法匯報(bào)數(shù)據(jù)源連接的占用情況。 
              通過Spring事務(wù)聲明,對JdbcUserServie的logon()方法進(jìn)行事務(wù)增強(qiáng),配置代碼如下所示: 
          Xml代碼  收藏代碼
          1. <?xml version="1.0" encoding="UTF-8" ?>  
          2. <beans xmlns="http://www.springframework.org/schema/beans"  
          3.       …  
          4.      http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">  
          5.     <context:component-scan base-package="com.baobaotao.connleak"/>  
          6.     <context:property-placeholder location="classpath:jdbc.properties"/>  
          7.     <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"  
          8.         destroy-method="close"   
          9.         p:driverClassName="${jdbc.driverClassName}"  
          10.         p:url="${jdbc.url}"   
          11.         p:username="${jdbc.username}"  
          12.         p:password="${jdbc.password}"/>  
          13.   
          14.     <bean id="jdbcTemplate"  
          15.           class="org.springframework.jdbc.core.JdbcTemplate"  
          16.           p:dataSource-ref="dataSource"/>  
          17.   
          18.     <bean id="transactionManager"  
          19.           class="org.springframework.jdbc.datasource.DataSourceTransactionManager"  
          20.           p:dataSource-ref="dataSource"/>  
          21.   
          22.      <!--①啟用注解驅(qū)動的事務(wù)增強(qiáng)-->  
          23.      <tx:annotation-driven/>        
          24. </beans>  

          然后,運(yùn)行JdbcUserServie,在控制臺將觀察到如下的輸出信息: 
          引用
          連接數(shù)[active:idle]-[0:0] 
          連接數(shù)[active:idle]-[2:0] 
          連接數(shù)[active:idle]-[1:1] 
          連接數(shù)[active:idle]-[3:0] 
          連接數(shù)[active:idle]-[2:1]

          我們通過表10-3對數(shù)據(jù)源連接的占用和泄漏情況進(jìn)行描述。 
          時間執(zhí)行線程1執(zhí)行線程2數(shù)據(jù)源連接
                         activeidleleak
          T0未啟動      未啟動000
          T1正在執(zhí)行方法 未啟動200
          T2執(zhí)行完畢  未啟動111
          T3執(zhí)行完畢  正式執(zhí)行方法301
          T4執(zhí)行完畢  執(zhí)行完畢212

             可見在執(zhí)行線程1執(zhí)行完畢后,只釋放了一個數(shù)據(jù)連接,還有一個數(shù)據(jù)連接處于active狀態(tài),說明泄漏了一個連接。相似的,執(zhí)行線程2執(zhí)行完畢后,也泄漏了一個連接:原因是直接通過數(shù)據(jù)源獲取連接(jdbcTemplate.getDataSource().getConnection())而沒有顯式釋放。 

          通過DataSourceUtils獲取數(shù)據(jù)連接 

              Spring提供了一個能從當(dāng)前事務(wù)上下文中獲取綁定的數(shù)據(jù)連接的工具類,那就是DataSourceUtils。Spring強(qiáng)調(diào)必須使用DataSourceUtils工具類獲取數(shù)據(jù)連接,Spring的JdbcTemplate內(nèi)部也是通過DataSourceUtils來獲取連接的。    DataSourceUtils提供了若干獲取和釋放數(shù)據(jù)連接的靜態(tài)方法,說明如下: 

          •   static Connection doGetConnection(DataSource dataSource):首先嘗試從事務(wù)上下文中獲取連接,失敗后再從數(shù)據(jù)源獲取連接;
          • static Connection getConnection(DataSource dataSource):和doGetConnection方法的功能一樣,實(shí)際上,它內(nèi)部就是調(diào)用doGetConnection方法獲取連接的;
          • static void doReleaseConnection(Connection con, DataSource dataSource):釋放連接,放回到連接池中;
          • static void releaseConnection(Connection con, DataSource dataSource):和doRelease Connection方法的功能一樣,實(shí)際上,它內(nèi)部就是調(diào)用doReleaseConnection方法獲取連接的。


          來看一下DataSourceUtils從數(shù)據(jù)源獲取連接的關(guān)鍵代碼: 

          Java代碼  收藏代碼
          1. public abstract class DataSourceUtils {  
          2. …  
          3. public static Connection doGetConnection(DataSource dataSource) throws SQLException {  
          4.         Assert.notNull(dataSource, "No DataSource specified");  
          5.   
          6.      //①首先嘗試從事務(wù)同步管理器中獲取數(shù)據(jù)連接  
          7.     ConnectionHolder conHolder =   
          8.            (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);  
          9.     if (conHolder != null && (conHolder.hasConnection() ||   
          10.                           conHolder.isSynchronizedWithTransaction())) {   
          11.             conHolder.requested();  
          12.             if (!conHolder.hasConnection()) {  
          13.                 logger.debug("Fetching resumed JDBC Connection from DataSource");  
          14.                 conHolder.setConnection(dataSource.getConnection());  
          15.             }  
          16.             return conHolder.getConnection();  
          17.         }  
          18.   
          19.           //②如果獲取不到連接,則直接從數(shù)據(jù)源中獲取連接  
          20.         Connection con = dataSource.getConnection();  
          21.   
          22.          //③如果擁有事務(wù)上下文,則將連接綁定到事務(wù)上下文中  
          23.         if (TransactionSynchronizationManager.isSynchronizationActive()) {  
          24.               ConnectionHolder holderToUse = conHolder;  
          25.             if (holderToUse == null) {  
          26.                 holderToUse = new ConnectionHolder(con);  
          27.             }  
          28.             else {holderToUse.setConnection(con);}  
          29.             holderToUse.requested();  
          30.             TransactionSynchronizationManager.registerSynchronization(  
          31.                               new ConnectionSynchronization(holderToUse, dataSource));  
          32.             holderToUse.setSynchronizedWithTransaction(true);  
          33.             if (holderToUse != conHolder) {  
          34.                 TransactionSynchronizationManager.bindResource(  
          35.                                                             dataSource, holderToUse);  
          36.             }  
          37.         }  
          38.         return con;  
          39.     }  
          40.      …  
          41. }  


              它首先查看當(dāng)前是否存在事務(wù)管理上下文,并嘗試從事務(wù)管理上下文獲取連接,如果獲取失敗,直接從數(shù)據(jù)源中獲取連接。在獲取連接后,如果當(dāng)前擁有事務(wù)上下文,則將連接綁定到事務(wù)上下文中。 
              我們在JdbcUserService中,使用DataSourceUtils.getConnection()替換直接從數(shù)據(jù)源中獲取連接的代碼: 
          Java代碼  收藏代碼
          1. package com.baobaotao.connleak;  
          2. …  
          3. @Service("jdbcUserService")  
          4. public class JdbcUserService {  
          5.     @Autowired  
          6.     private JdbcTemplate jdbcTemplate;  
          7.   
          8.     @Transactional  
          9.     public void logon(String userName) {  
          10.         try {  
          11.             //①使用DataSourceUtils獲取數(shù)據(jù)連接  
          12.           Connection conn =   
          13.                  DataSourceUtils.getConnection(jdbcTemplate.getDataSource());  
          14.             //Connection conn = jdbcTemplate.getDataSource().getConnection();  
          15.   
          16.             String sql = "UPDATE t_user SET last_logon_time=? WHERE user_name =?";  
          17.             jdbcTemplate.update(sql, System.currentTimeMillis(), userName);  
          18.             Thread.sleep(1000);   
          19.         } catch (Exception e) {  
          20.             e.printStackTrace();  
          21.         }  
          22.     }  
          23. }  


              重新運(yùn)行代碼,得到如下的執(zhí)行結(jié)果: 

          引用
          連接數(shù)[active:idle]-[0:0] 
          連接數(shù)[active:idle]-[1:0] 
          連接數(shù)[active:idle]-[0:1] 
          連接數(shù)[active:idle]-[1:0] 
          連接數(shù)[active:idle]-[0:1]


             對照上一節(jié)的輸出日志,我們可以看到已經(jīng)沒有連接泄漏的現(xiàn)象了。一個執(zhí)行線程在運(yùn)行JdbcUserService#logon()方法時,只占用一個連接,而且方法執(zhí)行完畢后,該連接馬上釋放。這說明通過DataSourceUtils.getConnection()方法確實(shí)獲取了方法所在事務(wù)上下文綁定的那個連接,而不是像原來那樣從數(shù)據(jù)源中獲取一個新的連接。 

          通過DataSourceUtils獲取數(shù)據(jù)連接 

             是否使用DataSourceUtils獲取數(shù)據(jù)連接就可以高枕無憂了呢?理想很美好,但現(xiàn)實(shí)很殘酷:如果DataSourceUtils在沒有事務(wù)上下文的方法中使用getConnection()獲取連接,依然會造成數(shù)據(jù)連接泄漏! 
             保持上面的代碼不變,將上面Spring配置文件中①處的Spring AOP事務(wù)增強(qiáng)配置的代碼注釋掉,重新運(yùn)行代碼清單10-23的代碼,將得到如下的輸出日志: 

          引用
          連接數(shù)[active:idle]-[0:0] 
          連接數(shù)[active:idle]-[1:1] 
          連接數(shù)[active:idle]-[1:1] 
          連接數(shù)[active:idle]-[2:1] 
          連接數(shù)[active:idle]-[2:1]


              我們通過下表對數(shù)據(jù)源連接的占用和泄漏情況進(jìn)行描述。 
              仔細(xì)對上表的執(zhí)行過程,我們發(fā)現(xiàn)在T1時,有事務(wù)上下文時的active為2,idle為0,而此時由于沒有事務(wù)管理,則active為1而idle也為1。這說明有事務(wù)上下文時,需要等到整個事務(wù)方法(即logon())返回后,事務(wù)上下文綁定的連接才被釋放。但在沒有事務(wù)上下文時,logon()調(diào)用JdbcTemplate執(zhí)行完數(shù)據(jù)操作后,馬上就釋放連接。 

          時間執(zhí)行線程1執(zhí)行線程2數(shù)據(jù)源連接
                         activeidleleak
          T0未啟動未啟動000
          T1正在執(zhí)行方法未啟動110
          T2執(zhí)行完畢未啟動111
          T3執(zhí)行完畢正式執(zhí)行方法211
          T4執(zhí)行完畢執(zhí)行完畢212


             在T2執(zhí)行線程完成logon()方法的調(diào)用后,有一個連接沒有被釋放(active),所以發(fā)生了連接泄漏。到T4時,兩個執(zhí)行線程都完成了logon()方法的調(diào)用,但是出現(xiàn)了兩個未釋放的連接。 
             要堵上這個連接泄漏的漏洞,需要對logon()方法進(jìn)行如下的改造: 
          Java代碼  收藏代碼
          1. package com.baobaotao.connleak;  
          2. …  
          3. @Service("jdbcUserService")  
          4. public class JdbcUserService {  
          5.     @Autowired  
          6.     private JdbcTemplate jdbcTemplate;  
          7.   
          8.     @Transactional  
          9.     public void logon(String userName) {  
          10.         try {  
          11.           Connection conn =   
          12.                  DataSourceUtils.getConnection(jdbcTemplate.getDataSource());  
          13.             String sql = "UPDATE t_user SET last_logon_time=? WHERE user_name =?";  
          14.             jdbcTemplate.update(sql, System.currentTimeMillis(), userName);  
          15.             Thread.sleep(1000);   
          16.             //①   
          17.         } catch (Exception e) {  
          18.             e.printStackTrace();  
          19.         }finally {  
          20.   
          21.             //②顯式使用DataSourceUtils釋放連接  
          22.             DataSourceUtils.releaseConnection(conn,jdbcTemplate.getDataSource());  
          23.         }  
          24.     }  
          25. }  

             在②處顯式調(diào)用DataSourceUtils.releaseConnection()方法釋放獲取的連接。特別需要指出的是:一定不能在①處釋放連接!因?yàn)槿绻鹟ogon()在獲取連接后,①處代碼前這段代碼執(zhí)行時發(fā)生異常,則①處釋放連接的動作將得不到執(zhí)行。這將是一個非常具有隱蔽性的連接泄漏的隱患點(diǎn)。 

          JdbcTemplate如何做到對連接泄漏的免疫 

               分析JdbcTemplate的代碼,我們可以清楚地看到它開放的每個數(shù)據(jù)操作方法,首先都使用DataSourceUtils獲取連接,在方法返回之前使用DataSourceUtils釋放連接。 
          來看一下JdbcTemplate最核心的一個數(shù)據(jù)操作方法execute(): 
          Java代碼  收藏代碼
          1. public <T> T execute(StatementCallback<T> action) throws DataAccessException {  
          2.   
          3.         //①首先根據(jù)DataSourceUtils獲取數(shù)據(jù)連接  
          4.          Connection con = DataSourceUtils.getConnection(getDataSource());  
          5.         Statement stmt = null;  
          6.         try {  
          7.             Connection conToUse = con;  
          8.             …  
          9.             handleWarnings(stmt);  
          10.             return result;  
          11.         }  
          12.         catch (SQLException ex) {  
          13.             JdbcUtils.closeStatement(stmt);  
          14.             stmt = null;  
          15.   
          16.               //②發(fā)生異常時,使用DataSourceUtils釋放數(shù)據(jù)連接  
          17.               DataSourceUtils.releaseConnection(con, getDataSource());  
          18.             con = null;  
          19.             throw getExceptionTranslator().translate(  
          20.                                 "StatementCallback", getSql(action), ex);  
          21.         }  
          22.         finally {  
          23.             JdbcUtils.closeStatement(stmt);  
          24.                 
          25.               //③最后再使用DataSourceUtils釋放數(shù)據(jù)連接  
          26.             DataSourceUtils.releaseConnection(con, getDataSource());  
          27.         }  
          28.     }  


              在①處通過DataSourceUtils.getConnection()獲取連接,在②和③處通過DataSourceUtils.releaseConnection()釋放連接。所有JdbcTemplate開放的數(shù)據(jù)訪問API最終都是直接或間接由execute(StatementCallback<T> action)方法執(zhí)行數(shù)據(jù)訪問操作的,因此這個方法代表了JdbcTemplate數(shù)據(jù)操作的最終實(shí)現(xiàn)方式。 
              正是因?yàn)镴dbcTemplate嚴(yán)謹(jǐn)?shù)墨@取連接及釋放連接的模式化流程保證了JdbcTemplate對數(shù)據(jù)連接泄漏問題的免疫性。所以,如有可能盡量使用JdbcTemplate、HibernateTemplate等這些模板進(jìn)行數(shù)據(jù)訪問操作,避免直接獲取數(shù)據(jù)連接的操作。 

          使用TransactionAwareDataSourceProxy 


              如果不得已要顯式獲取數(shù)據(jù)連接,除了使用DataSourceUtils獲取事務(wù)上下文綁定的連接外,還可以通過TransactionAwareDataSourceProxy對數(shù)據(jù)源進(jìn)行代理。數(shù)據(jù)源對象被代理后就具有了事務(wù)上下文感知的能力,通過代理數(shù)據(jù)源的getConnection()方法獲取連接和使用DataSourceUtils.getConnection()獲取連接的效果是一樣的。 
             下面是使用TransactionAwareDataSourceProxy對數(shù)據(jù)源進(jìn)行代理的配置: 

          Xml代碼  收藏代碼
          1. <?xml version="1.0" encoding="UTF-8" ?>  
          2. <beans xmlns="http://www.springframework.org/schema/beans"  
          3.        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
          4.        …  
          5.        http://www.springframework.org/schema/tx      
          6.        http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">  
          7.     <context:component-scan base-package="com.baobaotao.connleak"/>  
          8.     <context:property-placeholder location="classpath:jdbc.properties"/>  
          9.   
          10.     <!--①未被代理的數(shù)據(jù)源 -->  
          11.     <bean id="originDataSource" class="org.apache.commons.dbcp.BasicDataSource"  
          12.         destroy-method="close"   
          13.         p:driverClassName="${jdbc.driverClassName}"  
          14.         p:url="${jdbc.url}"   
          15.         p:username="${jdbc.username}"  
          16.         p:password="${jdbc.password}"/>  
          17.       
          18.      <!--②對數(shù)據(jù)源進(jìn)行代碼,使數(shù)據(jù)源具體事務(wù)上下文感知性 -->  
          19.     <bean id="dataSource"  
          20.         class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy"  
          21.         p:targetDataSource-ref="originDataSource" />  
          22.   
          23.     <bean id="jdbcTemplate"  
          24.           class="org.springframework.jdbc.core.JdbcTemplate"  
          25.           p:dataSource-ref="dataSource"/>  
          26.   
          27.     <bean id="transactionManager"  
          28.           class="org.springframework.jdbc.datasource.DataSourceTransactionManager"  
          29.           p:dataSource-ref="dataSource"/>           
          30.     <tx:annotation-driven/>        
          31. </beans>  


              對數(shù)據(jù)源進(jìn)行代理后,我們就可以通過數(shù)據(jù)源代理對象的getConnection()獲取事務(wù)上下文中綁定的數(shù)據(jù)連接了。因此,如果數(shù)據(jù)源已經(jīng)進(jìn)行了   TransactionAwareDataSourceProxy的代理,而且方法存在事務(wù)上下文,那么代碼清單10-19的代碼也不會生產(chǎn)連接泄漏的問題。 

          其他數(shù)據(jù)訪問技術(shù)的等價類 

              理解了Spring JDBC的數(shù)據(jù)連接泄漏問題,其中的道理可以平滑地推廣到其他框架中去。Spring為每個數(shù)據(jù)訪問技術(shù)框架都提供了一個獲取事務(wù)上下文綁定的數(shù)據(jù)連接(或其衍生品)的工具類和數(shù)據(jù)源(或其衍生品)的代理類。 
             表10-5列出了不同數(shù)據(jù)訪問技術(shù)對應(yīng)DataSourceUtils的等價類。 
          表10-5  不同數(shù)據(jù)訪問框架DataSourceUtils的等價類 
          數(shù)據(jù)訪問技術(shù)框架連接(或衍生品)獲取工具類                               
          Spring JDBCorg.springframework.jdbc.datasource.DataSourceUtils      
          Hibernateorg.springframework.orm.hibernate3.SessionFactoryUtils   
          iBatisorg.springframework.jdbc.datasource.DataSourceUtils      
          JPAorg.springframework.orm.jpa.EntityManagerFactoryUtils    
          JDOorg.springframework.orm.jdo.PersistenceManagerFactoryUtils


            表10-6列出了不同數(shù)據(jù)訪問技術(shù)框架下TransactionAwareDataSourceProxy的等價類。 
          表10-6  不同數(shù)據(jù)訪問框架TransactionAwareDataSourceProxy的等價類 
          數(shù)據(jù)訪問技術(shù)框架連接(或衍生品)獲取工具類                                                    
          Spring JDBCorg.springframework.jdbc.datasource.TransactionAwareDataSourceProxy       
          Hibernateorg.springframework.orm.hibernate3.LocalSessionFactoryBean                    
          iBatisorg.springframework.jdbc.datasource.TransactionAwareDataSourceProxy           
          JPA無                                                                        
          JDOorg.springframework.orm.jdo.TransactionAwarePersistenceManagerFactoryProxy


          注:以上內(nèi)容摘自《Spring 3.x企業(yè)應(yīng)用開發(fā)實(shí)戰(zhàn)》
          分享到:  
          評論
          2 樓 huang_yong 2012-04-14  
          寫程序的時候,如果是從底層獲取Connection,一定要注意在finally里面close掉,但是如果從Hibernate的SessionFactory級別獲取Connection,就無需手工close掉,這些工作都由Hibernate為我們完成了。

          一定要使用:

          Connection conn = SessionFactoryUtils.getDataSource(hibernateTemplate.getSessionFactory()).getConnection();

          獲取Connection,以上代碼可以進(jìn)行封裝,放入公共類中,方便其他程序員使用。
          1 樓 jinnianshilongnian 2012-03-09  
          1、打開連接不關(guān)閉 這是程序員犯的不該犯的錯誤(發(fā)現(xiàn)錯誤后可以改)

          2、當(dāng)有事務(wù)方法 特別慢,會拖慢整個應(yīng)用 甚至造成死鎖。
              以前我們用c3p0曾經(jīng)遇到過類似的,在用戶注冊高峰時,由于贈送積分/還有一些道具之類的是和另一個系統(tǒng)集成的,所以在高峰期特別慢,從而導(dǎo)致整個注冊方法特別慢,最后改成異步贈送(失敗了影響也是比較小的)。

          因此第一種情況,是可以查找并改正的。
          第二種情況,需要實(shí)際情況實(shí)際分析。

          posted on 2013-05-29 09:53 brock 閱讀(1031) 評論(0)  編輯  收藏 所屬分類: oracle 數(shù)據(jù)庫
          主站蜘蛛池模板: 长垣县| 博爱县| 望奎县| 丁青县| 江山市| 行唐县| 昌平区| 镇远县| 寿宁县| 永平县| 巴楚县| 金塔县| 冀州市| 遂昌县| 滕州市| 吉首市| 高要市| 莱阳市| 芮城县| 屏东县| 蒙山县| 密山市| 大埔区| 兴宁市| 九台市| 防城港市| 潮安县| 东丽区| 白水县| 霸州市| 莱西市| 宜兰县| 大田县| 安岳县| 轮台县| 定州市| 商都县| 诸城市| 宁乡县| 荆州市| 岑溪市|