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ù)連接泄漏的問題。
Java代碼 - package com.baobaotao.connleak;
- …
- @Service("jdbcUserService")
- public class JdbcUserService {
- @Autowired
- private JdbcTemplate jdbcTemplate;
-
- @Transactional
- public void logon(String userName) {
- try {
-
- //①直接從數(shù)據(jù)源獲取連接,后續(xù)程序沒有顯式釋放該連接
- Connection conn = jdbcTemplate.getDataSource().getConnection();
- String sql = "UPDATE t_user SET last_logon_time=? WHERE user_name =?";
- jdbcTemplate.update(sql, System.currentTimeMillis(), userName);
-
- //②模擬程序代碼的執(zhí)行時間
- Thread.sleep(1000);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
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代碼 - package com.baobaotao.connleak;
- …
- @Service("jdbcUserService")
- public class JdbcUserService {
- …
- //①以異步線程的方式執(zhí)行JdbcUserService#logon()方法,以模擬多線程的環(huán)境
- public static void asynchrLogon(JdbcUserService userService, String userName) {
- UserServiceRunner runner = new UserServiceRunner(userService, userName);
- runner.start();
- }
- private static class UserServiceRunner extends Thread {
- private JdbcUserService userService;
- private String userName;
- public UserServiceRunner(JdbcUserService userService, String userName) {
- this.userService = userService;
- this.userName = userName;
- }
- public void run() {
- userService.logon(userName);
- }
- }
-
- //②讓主執(zhí)行線程睡眠一段指定的時間
- public static void sleep(long time) {
- try {
- Thread.sleep(time);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
-
- //③匯報(bào)數(shù)據(jù)源的連接占用情況
- public static void reportConn(BasicDataSource basicDataSource) {
- System.out.println("連接數(shù)[active:idle]-[" +
- basicDataSource.getNumActive()+":"+basicDataSource.getNumIdle()+"]");
- }
-
- public static void main(String[] args) {
- ApplicationContext ctx =
- new ClassPathXmlApplicationContext("com/baobaotao/connleak/applicatonContext.xml");
- JdbcUserService userService = (JdbcUserService) ctx.getBean("jdbcUserService");
-
- BasicDataSource basicDataSource = (BasicDataSource) ctx.getBean("dataSource");
-
- //④匯報(bào)數(shù)據(jù)源初始連接占用情況
- JdbcUserService.reportConn(basicDataSource);
-
- JdbcUserService.asynchrLogon(userService, "tom");//啟動一個異常線程A
- JdbcUserService.sleep(500);
-
- //⑤此時線程A正在執(zhí)行JdbcUserService#logon()方法
- JdbcUserService.reportConn(basicDataSource);
-
- JdbcUserService.sleep(2000);
-
- //⑥此時線程A所執(zhí)行的JdbcUserService#logon()方法已經(jīng)執(zhí)行完畢
- JdbcUserService.reportConn(basicDataSource);
-
- JdbcUserService.asynchrLogon(userService, "john");//啟動一個異常線程B
- JdbcUserService.sleep(500);
-
- //⑦此時線程B正在執(zhí)行JdbcUserService#logon()方法
- JdbcUserService.reportConn(basicDataSource);
-
- JdbcUserService.sleep(2000);
-
- //⑧此時線程A和B都已完成JdbcUserService#logon()方法的執(zhí)行
- JdbcUserService.reportConn(basicDataSource);
-
- }
在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代碼 - <?xml version="1.0" encoding="UTF-8" ?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- …
- http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
- <context:component-scan base-package="com.baobaotao.connleak"/>
- <context:property-placeholder location="classpath:jdbc.properties"/>
- <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
- destroy-method="close"
- p:driverClassName="${jdbc.driverClassName}"
- p:url="${jdbc.url}"
- p:username="${jdbc.username}"
- p:password="${jdbc.password}"/>
-
- <bean id="jdbcTemplate"
- class="org.springframework.jdbc.core.JdbcTemplate"
- p:dataSource-ref="dataSource"/>
-
- <bean id="transactionManager"
- class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
- p:dataSource-ref="dataSource"/>
-
- <!--①啟用注解驅(qū)動的事務(wù)增強(qiáng)-->
- <tx:annotation-driven/>
- </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ù)源連接 active idle leak T0 未啟動 未啟動 0 0 0 T1 正在執(zhí)行方法 未啟動 2 0 0 T2 執(zhí)行完畢 未啟動 1 1 1 T3 執(zhí)行完畢 正式執(zhí)行方法 3 0 1 T4 執(zhí)行完畢 執(zhí)行完畢 2 1 2
可見在執(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代碼 - public abstract class DataSourceUtils {
- …
- public static Connection doGetConnection(DataSource dataSource) throws SQLException {
- Assert.notNull(dataSource, "No DataSource specified");
-
- //①首先嘗試從事務(wù)同步管理器中獲取數(shù)據(jù)連接
- 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();
- }
-
- //②如果獲取不到連接,則直接從數(shù)據(jù)源中獲取連接
- Connection con = dataSource.getConnection();
-
- //③如果擁有事務(wù)上下文,則將連接綁定到事務(wù)上下文中
- if (TransactionSynchronizationManager.isSynchronizationActive()) {
- ConnectionHolder holderToUse = conHolder;
- if (holderToUse == null) {
- holderToUse = new ConnectionHolder(con);
- }
- else {holderToUse.setConnection(con);}
- holderToUse.requested();
- TransactionSynchronizationManager.registerSynchronization(
- new ConnectionSynchronization(holderToUse, dataSource));
- holderToUse.setSynchronizedWithTransaction(true);
- if (holderToUse != conHolder) {
- TransactionSynchronizationManager.bindResource(
- dataSource, holderToUse);
- }
- }
- return con;
- }
- …
- }
它首先查看當(dāng)前是否存在事務(wù)管理上下文,并嘗試從事務(wù)管理上下文獲取連接,如果獲取失敗,直接從數(shù)據(jù)源中獲取連接。在獲取連接后,如果當(dāng)前擁有事務(wù)上下文,則將連接綁定到事務(wù)上下文中。
我們在JdbcUserService中,使用DataSourceUtils.getConnection()替換直接從數(shù)據(jù)源中獲取連接的代碼:
Java代碼 - package com.baobaotao.connleak;
- …
- @Service("jdbcUserService")
- public class JdbcUserService {
- @Autowired
- private JdbcTemplate jdbcTemplate;
-
- @Transactional
- public void logon(String userName) {
- try {
- //①使用DataSourceUtils獲取數(shù)據(jù)連接
- Connection conn =
- DataSourceUtils.getConnection(jdbcTemplate.getDataSource());
- //Connection conn = jdbcTemplate.getDataSource().getConnection();
-
- String sql = "UPDATE t_user SET last_logon_time=? WHERE user_name =?";
- jdbcTemplate.update(sql, System.currentTimeMillis(), userName);
- Thread.sleep(1000);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
重新運(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ù)源連接 active idle leak T0 未啟動 未啟動 0 0 0 T1 正在執(zhí)行方法 未啟動 1 1 0 T2 執(zhí)行完畢 未啟動 1 1 1 T3 執(zhí)行完畢 正式執(zhí)行方法 2 1 1 T4 執(zhí)行完畢 執(zhí)行完畢 2 1 2
在T2執(zhí)行線程完成logon()方法的調(diào)用后,有一個連接沒有被釋放(active),所以發(fā)生了連接泄漏。到T4時,兩個執(zhí)行線程都完成了logon()方法的調(diào)用,但是出現(xiàn)了兩個未釋放的連接。
要堵上這個連接泄漏的漏洞,需要對logon()方法進(jìn)行如下的改造:
Java代碼 - package com.baobaotao.connleak;
- …
- @Service("jdbcUserService")
- public class JdbcUserService {
- @Autowired
- private JdbcTemplate jdbcTemplate;
-
- @Transactional
- public void logon(String userName) {
- try {
- Connection conn =
- DataSourceUtils.getConnection(jdbcTemplate.getDataSource());
- String sql = "UPDATE t_user SET last_logon_time=? WHERE user_name =?";
- jdbcTemplate.update(sql, System.currentTimeMillis(), userName);
- Thread.sleep(1000);
- //①
- } catch (Exception e) {
- e.printStackTrace();
- }finally {
-
- //②顯式使用DataSourceUtils釋放連接
- DataSourceUtils.releaseConnection(conn,jdbcTemplate.getDataSource());
- }
- }
- }
在②處顯式調(diào)用DataSourceUtils.releaseConnection()方法釋放獲取的連接。特別需要指出的是:一定不能在①處釋放連接!因?yàn)槿绻鹟ogon()在獲取連接后,①處代碼前這段代碼執(zhí)行時發(fā)生異常,則①處釋放連接的動作將得不到執(zhí)行。這將是一個非常具有隱蔽性的連接泄漏的隱患點(diǎn)。
JdbcTemplate如何做到對連接泄漏的免疫
分析JdbcTemplate的代碼,我們可以清楚地看到它開放的每個數(shù)據(jù)操作方法,首先都使用DataSourceUtils獲取連接,在方法返回之前使用DataSourceUtils釋放連接。
來看一下JdbcTemplate最核心的一個數(shù)據(jù)操作方法execute():
Java代碼 - public <T> T execute(StatementCallback<T> action) throws DataAccessException {
-
- //①首先根據(jù)DataSourceUtils獲取數(shù)據(jù)連接
- Connection con = DataSourceUtils.getConnection(getDataSource());
- Statement stmt = null;
- try {
- Connection conToUse = con;
- …
- handleWarnings(stmt);
- return result;
- }
- catch (SQLException ex) {
- JdbcUtils.closeStatement(stmt);
- stmt = null;
-
- //②發(fā)生異常時,使用DataSourceUtils釋放數(shù)據(jù)連接
- DataSourceUtils.releaseConnection(con, getDataSource());
- con = null;
- throw getExceptionTranslator().translate(
- "StatementCallback", getSql(action), ex);
- }
- finally {
- JdbcUtils.closeStatement(stmt);
-
- //③最后再使用DataSourceUtils釋放數(shù)據(jù)連接
- DataSourceUtils.releaseConnection(con, getDataSource());
- }
- }
在①處通過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代碼 - <?xml version="1.0" encoding="UTF-8" ?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- …
- http://www.springframework.org/schema/tx
- http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
- <context:component-scan base-package="com.baobaotao.connleak"/>
- <context:property-placeholder location="classpath:jdbc.properties"/>
-
- <!--①未被代理的數(shù)據(jù)源 -->
- <bean id="originDataSource" class="org.apache.commons.dbcp.BasicDataSource"
- destroy-method="close"
- p:driverClassName="${jdbc.driverClassName}"
- p:url="${jdbc.url}"
- p:username="${jdbc.username}"
- p:password="${jdbc.password}"/>
-
- <!--②對數(shù)據(jù)源進(jìn)行代碼,使數(shù)據(jù)源具體事務(wù)上下文感知性 -->
- <bean id="dataSource"
- class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy"
- p:targetDataSource-ref="originDataSource" />
-
- <bean id="jdbcTemplate"
- class="org.springframework.jdbc.core.JdbcTemplate"
- p:dataSource-ref="dataSource"/>
-
- <bean id="transactionManager"
- class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
- p:dataSource-ref="dataSource"/>
- <tx:annotation-driven/>
- </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 JDBC org.springframework.jdbc.datasource.DataSourceUtils Hibernate org.springframework.orm.hibernate3.SessionFactoryUtils iBatis org.springframework.jdbc.datasource.DataSourceUtils JPA org.springframework.orm.jpa.EntityManagerFactoryUtils JDO org.springframework.orm.jdo.PersistenceManagerFactoryUtils
表10-6列出了不同數(shù)據(jù)訪問技術(shù)框架下TransactionAwareDataSourceProxy的等價類。
表10-6 不同數(shù)據(jù)訪問框架TransactionAwareDataSourceProxy的等價類
數(shù)據(jù)訪問技術(shù)框架 連接(或衍生品)獲取工具類 Spring JDBC org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy Hibernate org.springframework.orm.hibernate3.LocalSessionFactoryBean iBatis org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy JPA 無 JDO org.springframework.orm.jdo.TransactionAwarePersistenceManagerFactoryProxy
注:以上內(nèi)容摘自《Spring 3.x企業(yè)應(yīng)用開發(fā)實(shí)戰(zhàn)》- 2012-03-07 10:53
- 瀏覽 1547
- 評論(2)
- 論壇回復(fù) / 瀏覽 (5 / 2492)
- 分類:編程語言
- 相關(guā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í)際分析。
對于應(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ù)連接泄漏的問題。
Java代碼
- package com.baobaotao.connleak;
- …
- @Service("jdbcUserService")
- public class JdbcUserService {
- @Autowired
- private JdbcTemplate jdbcTemplate;
- @Transactional
- public void logon(String userName) {
- try {
- //①直接從數(shù)據(jù)源獲取連接,后續(xù)程序沒有顯式釋放該連接
- Connection conn = jdbcTemplate.getDataSource().getConnection();
- String sql = "UPDATE t_user SET last_logon_time=? WHERE user_name =?";
- jdbcTemplate.update(sql, System.currentTimeMillis(), userName);
- //②模擬程序代碼的執(zhí)行時間
- Thread.sleep(1000);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
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代碼
- package com.baobaotao.connleak;
- …
- @Service("jdbcUserService")
- public class JdbcUserService {
- …
- //①以異步線程的方式執(zhí)行JdbcUserService#logon()方法,以模擬多線程的環(huán)境
- public static void asynchrLogon(JdbcUserService userService, String userName) {
- UserServiceRunner runner = new UserServiceRunner(userService, userName);
- runner.start();
- }
- private static class UserServiceRunner extends Thread {
- private JdbcUserService userService;
- private String userName;
- public UserServiceRunner(JdbcUserService userService, String userName) {
- this.userService = userService;
- this.userName = userName;
- }
- public void run() {
- userService.logon(userName);
- }
- }
- //②讓主執(zhí)行線程睡眠一段指定的時間
- public static void sleep(long time) {
- try {
- Thread.sleep(time);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- //③匯報(bào)數(shù)據(jù)源的連接占用情況
- public static void reportConn(BasicDataSource basicDataSource) {
- System.out.println("連接數(shù)[active:idle]-[" +
- basicDataSource.getNumActive()+":"+basicDataSource.getNumIdle()+"]");
- }
- public static void main(String[] args) {
- ApplicationContext ctx =
- new ClassPathXmlApplicationContext("com/baobaotao/connleak/applicatonContext.xml");
- JdbcUserService userService = (JdbcUserService) ctx.getBean("jdbcUserService");
- BasicDataSource basicDataSource = (BasicDataSource) ctx.getBean("dataSource");
- //④匯報(bào)數(shù)據(jù)源初始連接占用情況
- JdbcUserService.reportConn(basicDataSource);
- JdbcUserService.asynchrLogon(userService, "tom");//啟動一個異常線程A
- JdbcUserService.sleep(500);
- //⑤此時線程A正在執(zhí)行JdbcUserService#logon()方法
- JdbcUserService.reportConn(basicDataSource);
- JdbcUserService.sleep(2000);
- //⑥此時線程A所執(zhí)行的JdbcUserService#logon()方法已經(jīng)執(zhí)行完畢
- JdbcUserService.reportConn(basicDataSource);
- JdbcUserService.asynchrLogon(userService, "john");//啟動一個異常線程B
- JdbcUserService.sleep(500);
- //⑦此時線程B正在執(zhí)行JdbcUserService#logon()方法
- JdbcUserService.reportConn(basicDataSource);
- JdbcUserService.sleep(2000);
- //⑧此時線程A和B都已完成JdbcUserService#logon()方法的執(zhí)行
- JdbcUserService.reportConn(basicDataSource);
- }
在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代碼
- <?xml version="1.0" encoding="UTF-8" ?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- …
- http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
- <context:component-scan base-package="com.baobaotao.connleak"/>
- <context:property-placeholder location="classpath:jdbc.properties"/>
- <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
- destroy-method="close"
- p:driverClassName="${jdbc.driverClassName}"
- p:url="${jdbc.url}"
- p:username="${jdbc.username}"
- p:password="${jdbc.password}"/>
- <bean id="jdbcTemplate"
- class="org.springframework.jdbc.core.JdbcTemplate"
- p:dataSource-ref="dataSource"/>
- <bean id="transactionManager"
- class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
- p:dataSource-ref="dataSource"/>
- <!--①啟用注解驅(qū)動的事務(wù)增強(qiáng)-->
- <tx:annotation-driven/>
- </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]
連接數(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ù)源連接 | ||
active | idle | leak | |||
T0 | 未啟動 | 未啟動 | 0 | 0 | 0 |
T1 | 正在執(zhí)行方法 | 未啟動 | 2 | 0 | 0 |
T2 | 執(zhí)行完畢 | 未啟動 | 1 | 1 | 1 |
T3 | 執(zhí)行完畢 | 正式執(zhí)行方法 | 3 | 0 | 1 |
T4 | 執(zhí)行完畢 | 執(zhí)行完畢 | 2 | 1 | 2 |
可見在執(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代碼
- public abstract class DataSourceUtils {
- …
- public static Connection doGetConnection(DataSource dataSource) throws SQLException {
- Assert.notNull(dataSource, "No DataSource specified");
- //①首先嘗試從事務(wù)同步管理器中獲取數(shù)據(jù)連接
- 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();
- }
- //②如果獲取不到連接,則直接從數(shù)據(jù)源中獲取連接
- Connection con = dataSource.getConnection();
- //③如果擁有事務(wù)上下文,則將連接綁定到事務(wù)上下文中
- if (TransactionSynchronizationManager.isSynchronizationActive()) {
- ConnectionHolder holderToUse = conHolder;
- if (holderToUse == null) {
- holderToUse = new ConnectionHolder(con);
- }
- else {holderToUse.setConnection(con);}
- holderToUse.requested();
- TransactionSynchronizationManager.registerSynchronization(
- new ConnectionSynchronization(holderToUse, dataSource));
- holderToUse.setSynchronizedWithTransaction(true);
- if (holderToUse != conHolder) {
- TransactionSynchronizationManager.bindResource(
- dataSource, holderToUse);
- }
- }
- return con;
- }
- …
- }
它首先查看當(dāng)前是否存在事務(wù)管理上下文,并嘗試從事務(wù)管理上下文獲取連接,如果獲取失敗,直接從數(shù)據(jù)源中獲取連接。在獲取連接后,如果當(dāng)前擁有事務(wù)上下文,則將連接綁定到事務(wù)上下文中。
我們在JdbcUserService中,使用DataSourceUtils.getConnection()替換直接從數(shù)據(jù)源中獲取連接的代碼:
Java代碼
- package com.baobaotao.connleak;
- …
- @Service("jdbcUserService")
- public class JdbcUserService {
- @Autowired
- private JdbcTemplate jdbcTemplate;
- @Transactional
- public void logon(String userName) {
- try {
- //①使用DataSourceUtils獲取數(shù)據(jù)連接
- Connection conn =
- DataSourceUtils.getConnection(jdbcTemplate.getDataSource());
- //Connection conn = jdbcTemplate.getDataSource().getConnection();
- String sql = "UPDATE t_user SET last_logon_time=? WHERE user_name =?";
- jdbcTemplate.update(sql, System.currentTimeMillis(), userName);
- Thread.sleep(1000);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
重新運(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]
連接數(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ù)[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ù)源連接 | ||
active | idle | leak | |||
T0 | 未啟動 | 未啟動 | 0 | 0 | 0 |
T1 | 正在執(zhí)行方法 | 未啟動 | 1 | 1 | 0 |
T2 | 執(zhí)行完畢 | 未啟動 | 1 | 1 | 1 |
T3 | 執(zhí)行完畢 | 正式執(zhí)行方法 | 2 | 1 | 1 |
T4 | 執(zhí)行完畢 | 執(zhí)行完畢 | 2 | 1 | 2 |
在T2執(zhí)行線程完成logon()方法的調(diào)用后,有一個連接沒有被釋放(active),所以發(fā)生了連接泄漏。到T4時,兩個執(zhí)行線程都完成了logon()方法的調(diào)用,但是出現(xiàn)了兩個未釋放的連接。
要堵上這個連接泄漏的漏洞,需要對logon()方法進(jìn)行如下的改造:
Java代碼
- package com.baobaotao.connleak;
- …
- @Service("jdbcUserService")
- public class JdbcUserService {
- @Autowired
- private JdbcTemplate jdbcTemplate;
- @Transactional
- public void logon(String userName) {
- try {
- Connection conn =
- DataSourceUtils.getConnection(jdbcTemplate.getDataSource());
- String sql = "UPDATE t_user SET last_logon_time=? WHERE user_name =?";
- jdbcTemplate.update(sql, System.currentTimeMillis(), userName);
- Thread.sleep(1000);
- //①
- } catch (Exception e) {
- e.printStackTrace();
- }finally {
- //②顯式使用DataSourceUtils釋放連接
- DataSourceUtils.releaseConnection(conn,jdbcTemplate.getDataSource());
- }
- }
- }
在②處顯式調(diào)用DataSourceUtils.releaseConnection()方法釋放獲取的連接。特別需要指出的是:一定不能在①處釋放連接!因?yàn)槿绻鹟ogon()在獲取連接后,①處代碼前這段代碼執(zhí)行時發(fā)生異常,則①處釋放連接的動作將得不到執(zhí)行。這將是一個非常具有隱蔽性的連接泄漏的隱患點(diǎn)。
JdbcTemplate如何做到對連接泄漏的免疫
分析JdbcTemplate的代碼,我們可以清楚地看到它開放的每個數(shù)據(jù)操作方法,首先都使用DataSourceUtils獲取連接,在方法返回之前使用DataSourceUtils釋放連接。
來看一下JdbcTemplate最核心的一個數(shù)據(jù)操作方法execute():
Java代碼
- public <T> T execute(StatementCallback<T> action) throws DataAccessException {
- //①首先根據(jù)DataSourceUtils獲取數(shù)據(jù)連接
- Connection con = DataSourceUtils.getConnection(getDataSource());
- Statement stmt = null;
- try {
- Connection conToUse = con;
- …
- handleWarnings(stmt);
- return result;
- }
- catch (SQLException ex) {
- JdbcUtils.closeStatement(stmt);
- stmt = null;
- //②發(fā)生異常時,使用DataSourceUtils釋放數(shù)據(jù)連接
- DataSourceUtils.releaseConnection(con, getDataSource());
- con = null;
- throw getExceptionTranslator().translate(
- "StatementCallback", getSql(action), ex);
- }
- finally {
- JdbcUtils.closeStatement(stmt);
- //③最后再使用DataSourceUtils釋放數(shù)據(jù)連接
- DataSourceUtils.releaseConnection(con, getDataSource());
- }
- }
在①處通過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代碼
- <?xml version="1.0" encoding="UTF-8" ?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- …
- http://www.springframework.org/schema/tx
- http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
- <context:component-scan base-package="com.baobaotao.connleak"/>
- <context:property-placeholder location="classpath:jdbc.properties"/>
- <!--①未被代理的數(shù)據(jù)源 -->
- <bean id="originDataSource" class="org.apache.commons.dbcp.BasicDataSource"
- destroy-method="close"
- p:driverClassName="${jdbc.driverClassName}"
- p:url="${jdbc.url}"
- p:username="${jdbc.username}"
- p:password="${jdbc.password}"/>
- <!--②對數(shù)據(jù)源進(jìn)行代碼,使數(shù)據(jù)源具體事務(wù)上下文感知性 -->
- <bean id="dataSource"
- class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy"
- p:targetDataSource-ref="originDataSource" />
- <bean id="jdbcTemplate"
- class="org.springframework.jdbc.core.JdbcTemplate"
- p:dataSource-ref="dataSource"/>
- <bean id="transactionManager"
- class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
- p:dataSource-ref="dataSource"/>
- <tx:annotation-driven/>
- </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 JDBC | org.springframework.jdbc.datasource.DataSourceUtils |
Hibernate | org.springframework.orm.hibernate3.SessionFactoryUtils |
iBatis | org.springframework.jdbc.datasource.DataSourceUtils |
JPA | org.springframework.orm.jpa.EntityManagerFactoryUtils |
JDO | org.springframework.orm.jdo.PersistenceManagerFactoryUtils |
表10-6列出了不同數(shù)據(jù)訪問技術(shù)框架下TransactionAwareDataSourceProxy的等價類。
表10-6 不同數(shù)據(jù)訪問框架TransactionAwareDataSourceProxy的等價類
數(shù)據(jù)訪問技術(shù)框架 | 連接(或衍生品)獲取工具類 |
Spring JDBC | org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy |
Hibernate | org.springframework.orm.hibernate3.LocalSessionFactoryBean |
iBatis | org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy |
JPA | 無 |
JDO | org.springframework.orm.jdo.TransactionAwarePersistenceManagerFactoryProxy |
注:以上內(nèi)容摘自《Spring 3.x企業(yè)應(yīng)用開發(fā)實(shí)戰(zhàn)》
- 2012-03-07 10:53
- 瀏覽 1547
- 評論(2)
- 論壇回復(fù) / 瀏覽 (5 / 2492)
- 分類:編程語言
- 相關(guā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)行封裝,放入公共類中,方便其他程序員使用。
一定要使用:
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í)際分析。
2、當(dāng)有事務(wù)方法 特別慢,會拖慢整個應(yīng)用 甚至造成死鎖。
以前我們用c3p0曾經(jīng)遇到過類似的,在用戶注冊高峰時,由于贈送積分/還有一些道具之類的是和另一個系統(tǒng)集成的,所以在高峰期特別慢,從而導(dǎo)致整個注冊方法特別慢,最后改成異步贈送(失敗了影響也是比較小的)。
因此第一種情況,是可以查找并改正的。
第二種情況,需要實(shí)際情況實(shí)際分析。