- 浏览: 1530587 次
- 性别:
- 来自: 厦门
博客专栏
-
Spring 3.x企业实...
浏览量:465124
文章分类
最新评论
-
JyeChou:
学习Spring必学的Java基础知识(1)----反射 -
hhzhaoheng:
...
《Spring4.x企业应用开发实战》光盘资料下载 -
renlongnian:
//assertReflectionEquals(user1, ...
单元测试系列之3:测试整合之王Unitils -
骑着蜗牛超F1:
huang_yong 写道我的经验是,只需定义三层:1.ent ...
Spring的事务管理难点剖析(2):应用分层的迷惑 -
wangyudong:
工具地址貌似更新了哦https://github.com/Wi ...
几种常用的REST webservice客户端测试工具
底层连接资源的访问问题
对于应用开发者来说,数据连接泄漏无疑是一个可怕的梦魇。只要你开发的应用存在数据连接泄漏的问题,应用程序最终都将因数据连接资源的耗尽而崩溃,甚至还可能引起数据库的崩溃。数据连接泄漏像一个黑洞那样让开发者避之唯恐不及。
Spring DAO对所有支持的数据访问技术框架都使用模板化技术进行了薄层的封装。只要你的程序都使用Spring DAO的模板(如JdbcTemplate、HibernateTemplate等)进行数据访问,一定不会存在数据连接泄漏的问题——这是Spring给予我们的郑重承诺!如果使用Spring DAO模板进行数据操作,我们无须关注数据连接(Connection)及其衍生品(Hibernate的Session等)的获取和释放操作,模板类已经通过其内部流程替我们完成了,且对开发者是透明的。
但是由于集成第三方产品、整合遗产代码等原因,可能需要直接访问数据源或直接获取数据连接及其衍生品。这时,如果使用不当,就可能在无意中创造出一个魔鬼般的连接泄漏问题。
我们知道:当Spring事务方法运行时,就产生一个事务上下文,该上下文在本事务执行线程中针对同一个数据源绑定了一个唯一的数据连接(或其衍生品),所有被该事务上下文传播的方法都共享这个数据连接。这个数据连接从数据源获取及返回给数据源都在Spring掌控之中,不会发生问题。如果在需要数据连接时,能够获取这个被Spring管控的数据连接,则使用者可以放心使用,无须关注连接释放的问题。
那么,如何获取这些被Spring管控的数据连接呢?Spring提供了两种方法:其一是使用数据资源获取工具类;其二是对数据源(或其衍生品如Hibernate SessionFactory)进行代理。
Spring JDBC数据连接泄漏
如果我们从数据源直接获取连接,且在使用完成后不主动归还给数据源(调用Connection#close()),则将造成数据连接泄漏的问题。
JdbcUserService通过Spring AOP事务增强的配置,让所有public方法都工作在事务环境中,即让logon()和updateLastLogonTime()方法拥有事务功能。在logon()方法内部,我们在①处通过调用jdbcTemplate.getDataSource().getConnection()显式获取一个连接,这个连接不是logon()方法事务上下文线程绑定的连接,所以如果开发者没有手工释放这个连接(显式调用Connection#close()方法),则这个连接将永久被占用(处于active状态),造成连接泄漏!下面,我们编写模拟运行的代码,查看方法执行对数据连接的实际占用情况:
在JdbcUserService中添加一个可异步执行logon()方法的asynchrLogon()方法,我们通过异步执行logon()以及让主线程睡眠的方式模拟多线程环境下的执行场景。在不同的执行点,通过reportConn()方法汇报数据源连接的占用情况。
通过Spring事务声明,对JdbcUserServie的logon()方法进行事务增强,配置代码如下所示:
然后,运行JdbcUserServie,在控制台将观察到如下的输出信息:
我们通过表10-3对数据源连接的占用和泄漏情况进行描述。
可见在执行线程1执行完毕后,只释放了一个数据连接,还有一个数据连接处于active状态,说明泄漏了一个连接。相似的,执行线程2执行完毕后,也泄漏了一个连接:原因是直接通过数据源获取连接(jdbcTemplate.getDataSource().getConnection())而没有显式释放。
通过DataSourceUtils获取数据连接
Spring提供了一个能从当前事务上下文中获取绑定的数据连接的工具类,那就是DataSourceUtils。Spring强调必须使用DataSourceUtils工具类获取数据连接,Spring的JdbcTemplate内部也是通过DataSourceUtils来获取连接的。 DataSourceUtils提供了若干获取和释放数据连接的静态方法,说明如下:
来看一下DataSourceUtils从数据源获取连接的关键代码:
它首先查看当前是否存在事务管理上下文,并尝试从事务管理上下文获取连接,如果获取失败,直接从数据源中获取连接。在获取连接后,如果当前拥有事务上下文,则将连接绑定到事务上下文中。
我们在JdbcUserService中,使用DataSourceUtils.getConnection()替换直接从数据源中获取连接的代码:
重新运行代码,得到如下的执行结果:
对照上一节的输出日志,我们可以看到已经没有连接泄漏的现象了。一个执行线程在运行JdbcUserService#logon()方法时,只占用一个连接,而且方法执行完毕后,该连接马上释放。这说明通过DataSourceUtils.getConnection()方法确实获取了方法所在事务上下文绑定的那个连接,而不是像原来那样从数据源中获取一个新的连接。
通过DataSourceUtils获取数据连接
是否使用DataSourceUtils获取数据连接就可以高枕无忧了呢?理想很美好,但现实很残酷:如果DataSourceUtils在没有事务上下文的方法中使用getConnection()获取连接,依然会造成数据连接泄漏!
保持上面的代码不变,将上面Spring配置文件中①处的Spring AOP事务增强配置的代码注释掉,重新运行代码清单10-23的代码,将得到如下的输出日志:
我们通过下表对数据源连接的占用和泄漏情况进行描述。
仔细对上表的执行过程,我们发现在T1时,有事务上下文时的active为2,idle为0,而此时由于没有事务管理,则active为1而idle也为1。这说明有事务上下文时,需要等到整个事务方法(即logon())返回后,事务上下文绑定的连接才被释放。但在没有事务上下文时,logon()调用JdbcTemplate执行完数据操作后,马上就释放连接。
在T2执行线程完成logon()方法的调用后,有一个连接没有被释放(active),所以发生了连接泄漏。到T4时,两个执行线程都完成了logon()方法的调用,但是出现了两个未释放的连接。
要堵上这个连接泄漏的漏洞,需要对logon()方法进行如下的改造:
在②处显式调用DataSourceUtils.releaseConnection()方法释放获取的连接。特别需要指出的是:一定不能在①处释放连接!因为如果logon()在获取连接后,①处代码前这段代码执行时发生异常,则①处释放连接的动作将得不到执行。这将是一个非常具有隐蔽性的连接泄漏的隐患点。
JdbcTemplate如何做到对连接泄漏的免疫
分析JdbcTemplate的代码,我们可以清楚地看到它开放的每个数据操作方法,首先都使用DataSourceUtils获取连接,在方法返回之前使用DataSourceUtils释放连接。
来看一下JdbcTemplate最核心的一个数据操作方法execute():
在①处通过DataSourceUtils.getConnection()获取连接,在②和③处通过DataSourceUtils.releaseConnection()释放连接。所有JdbcTemplate开放的数据访问API最终都是直接或间接由execute(StatementCallback<T> action)方法执行数据访问操作的,因此这个方法代表了JdbcTemplate数据操作的最终实现方式。
正是因为JdbcTemplate严谨的获取连接及释放连接的模式化流程保证了JdbcTemplate对数据连接泄漏问题的免疫性。所以,如有可能尽量使用JdbcTemplate、HibernateTemplate等这些模板进行数据访问操作,避免直接获取数据连接的操作。
使用TransactionAwareDataSourceProxy
如果不得已要显式获取数据连接,除了使用DataSourceUtils获取事务上下文绑定的连接外,还可以通过TransactionAwareDataSourceProxy对数据源进行代理。数据源对象被代理后就具有了事务上下文感知的能力,通过代理数据源的getConnection()方法获取连接和使用DataSourceUtils.getConnection()获取连接的效果是一样的。
下面是使用TransactionAwareDataSourceProxy对数据源进行代理的配置:
对数据源进行代理后,我们就可以通过数据源代理对象的getConnection()获取事务上下文中绑定的数据连接了。因此,如果数据源已经进行了 TransactionAwareDataSourceProxy的代理,而且方法存在事务上下文,那么代码清单10-19的代码也不会生产连接泄漏的问题。
其他数据访问技术的等价类
理解了Spring JDBC的数据连接泄漏问题,其中的道理可以平滑地推广到其他框架中去。Spring为每个数据访问技术框架都提供了一个获取事务上下文绑定的数据连接(或其衍生品)的工具类和数据源(或其衍生品)的代理类。
表10-5列出了不同数据访问技术对应DataSourceUtils的等价类。
表10-5 不同数据访问框架DataSourceUtils的等价类
表10-6列出了不同数据访问技术框架下TransactionAwareDataSourceProxy的等价类。
表10-6 不同数据访问框架TransactionAwareDataSourceProxy的等价类
注:以上内容摘自《Spring 4.x企业应用开发实战》
对于应用开发者来说,数据连接泄漏无疑是一个可怕的梦魇。只要你开发的应用存在数据连接泄漏的问题,应用程序最终都将因数据连接资源的耗尽而崩溃,甚至还可能引起数据库的崩溃。数据连接泄漏像一个黑洞那样让开发者避之唯恐不及。
Spring DAO对所有支持的数据访问技术框架都使用模板化技术进行了薄层的封装。只要你的程序都使用Spring DAO的模板(如JdbcTemplate、HibernateTemplate等)进行数据访问,一定不会存在数据连接泄漏的问题——这是Spring给予我们的郑重承诺!如果使用Spring DAO模板进行数据操作,我们无须关注数据连接(Connection)及其衍生品(Hibernate的Session等)的获取和释放操作,模板类已经通过其内部流程替我们完成了,且对开发者是透明的。
但是由于集成第三方产品、整合遗产代码等原因,可能需要直接访问数据源或直接获取数据连接及其衍生品。这时,如果使用不当,就可能在无意中创造出一个魔鬼般的连接泄漏问题。
我们知道:当Spring事务方法运行时,就产生一个事务上下文,该上下文在本事务执行线程中针对同一个数据源绑定了一个唯一的数据连接(或其衍生品),所有被该事务上下文传播的方法都共享这个数据连接。这个数据连接从数据源获取及返回给数据源都在Spring掌控之中,不会发生问题。如果在需要数据连接时,能够获取这个被Spring管控的数据连接,则使用者可以放心使用,无须关注连接释放的问题。
那么,如何获取这些被Spring管控的数据连接呢?Spring提供了两种方法:其一是使用数据资源获取工具类;其二是对数据源(或其衍生品如Hibernate SessionFactory)进行代理。
Spring JDBC数据连接泄漏
如果我们从数据源直接获取连接,且在使用完成后不主动归还给数据源(调用Connection#close()),则将造成数据连接泄漏的问题。
package com.baobaotao.connleak; … @Service("jdbcUserService") public class JdbcUserService { @Autowired private JdbcTemplate jdbcTemplate; @Transactional public void logon(String userName) { try { //①直接从数据源获取连接,后续程序没有显式释放该连接 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(); } } }
JdbcUserService通过Spring AOP事务增强的配置,让所有public方法都工作在事务环境中,即让logon()和updateLastLogonTime()方法拥有事务功能。在logon()方法内部,我们在①处通过调用jdbcTemplate.getDataSource().getConnection()显式获取一个连接,这个连接不是logon()方法事务上下文线程绑定的连接,所以如果开发者没有手工释放这个连接(显式调用Connection#close()方法),则这个连接将永久被占用(处于active状态),造成连接泄漏!下面,我们编写模拟运行的代码,查看方法执行对数据连接的实际占用情况:
package com.baobaotao.connleak; … @Service("jdbcUserService") public class JdbcUserService { … //①以异步线程的方式执行JdbcUserService#logon()方法,以模拟多线程的环境 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); } } //②让主执行线程睡眠一段指定的时间 public static void sleep(long time) { try { Thread.sleep(time); } catch (InterruptedException e) { e.printStackTrace(); } } //③汇报数据源的连接占用情况 public static void reportConn(BasicDataSource basicDataSource) { System.out.println("连接数[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"); //④汇报数据源初始连接占用情况 JdbcUserService.reportConn(basicDataSource); JdbcUserService.asynchrLogon(userService, "tom");//启动一个异常线程A JdbcUserService.sleep(500); //⑤此时线程A正在执行JdbcUserService#logon()方法 JdbcUserService.reportConn(basicDataSource); JdbcUserService.sleep(2000); //⑥此时线程A所执行的JdbcUserService#logon()方法已经执行完毕 JdbcUserService.reportConn(basicDataSource); JdbcUserService.asynchrLogon(userService, "john");//启动一个异常线程B JdbcUserService.sleep(500); //⑦此时线程B正在执行JdbcUserService#logon()方法 JdbcUserService.reportConn(basicDataSource); JdbcUserService.sleep(2000); //⑧此时线程A和B都已完成JdbcUserService#logon()方法的执行 JdbcUserService.reportConn(basicDataSource); }
在JdbcUserService中添加一个可异步执行logon()方法的asynchrLogon()方法,我们通过异步执行logon()以及让主线程睡眠的方式模拟多线程环境下的执行场景。在不同的执行点,通过reportConn()方法汇报数据源连接的占用情况。
通过Spring事务声明,对JdbcUserServie的logon()方法进行事务增强,配置代码如下所示:
<?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"/> <!--①启用注解驱动的事务增强--> <tx:annotation-driven/> </beans>
然后,运行JdbcUserServie,在控制台将观察到如下的输出信息:
引用
连接数[active:idle]-[0:0]
连接数[active:idle]-[2:0]
连接数[active:idle]-[1:1]
连接数[active:idle]-[3:0]
连接数[active:idle]-[2:1]
连接数[active:idle]-[2:0]
连接数[active:idle]-[1:1]
连接数[active:idle]-[3:0]
连接数[active:idle]-[2:1]
我们通过表10-3对数据源连接的占用和泄漏情况进行描述。
时间 | 执行线程1 | 执行线程2 | 数据源连接 | ||
active | idle | leak | |||
T0 | 未启动 | 未启动 | 0 | 0 | 0 |
T1 | 正在执行方法 | 未启动 | 2 | 0 | 0 |
T2 | 执行完毕 | 未启动 | 1 | 1 | 1 |
T3 | 执行完毕 | 正式执行方法 | 3 | 0 | 1 |
T4 | 执行完毕 | 执行完毕 | 2 | 1 | 2 |
可见在执行线程1执行完毕后,只释放了一个数据连接,还有一个数据连接处于active状态,说明泄漏了一个连接。相似的,执行线程2执行完毕后,也泄漏了一个连接:原因是直接通过数据源获取连接(jdbcTemplate.getDataSource().getConnection())而没有显式释放。
通过DataSourceUtils获取数据连接
Spring提供了一个能从当前事务上下文中获取绑定的数据连接的工具类,那就是DataSourceUtils。Spring强调必须使用DataSourceUtils工具类获取数据连接,Spring的JdbcTemplate内部也是通过DataSourceUtils来获取连接的。 DataSourceUtils提供了若干获取和释放数据连接的静态方法,说明如下:
- static Connection doGetConnection(DataSource dataSource):首先尝试从事务上下文中获取连接,失败后再从数据源获取连接;
- static Connection getConnection(DataSource dataSource):和doGetConnection方法的功能一样,实际上,它内部就是调用doGetConnection方法获取连接的;
- static void doReleaseConnection(Connection con, DataSource dataSource):释放连接,放回到连接池中;
- static void releaseConnection(Connection con, DataSource dataSource):和doRelease Connection方法的功能一样,实际上,它内部就是调用doReleaseConnection方法获取连接的。
来看一下DataSourceUtils从数据源获取连接的关键代码:
public abstract class DataSourceUtils { … public static Connection doGetConnection(DataSource dataSource) 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("Fetching resumed JDBC Connection from DataSource"); conHolder.setConnection(dataSource.getConnection()); } return conHolder.getConnection(); } //②如果获取不到连接,则直接从数据源中获取连接 Connection con = dataSource.getConnection(); //③如果拥有事务上下文,则将连接绑定到事务上下文中 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; } … }
它首先查看当前是否存在事务管理上下文,并尝试从事务管理上下文获取连接,如果获取失败,直接从数据源中获取连接。在获取连接后,如果当前拥有事务上下文,则将连接绑定到事务上下文中。
我们在JdbcUserService中,使用DataSourceUtils.getConnection()替换直接从数据源中获取连接的代码:
package com.baobaotao.connleak; … @Service("jdbcUserService") public class JdbcUserService { @Autowired private JdbcTemplate jdbcTemplate; @Transactional public void logon(String userName) { try { //①使用DataSourceUtils获取数据连接 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(); } } }
重新运行代码,得到如下的执行结果:
引用
连接数[active:idle]-[0:0]
连接数[active:idle]-[1:0]
连接数[active:idle]-[0:1]
连接数[active:idle]-[1:0]
连接数[active:idle]-[0:1]
连接数[active:idle]-[1:0]
连接数[active:idle]-[0:1]
连接数[active:idle]-[1:0]
连接数[active:idle]-[0:1]
对照上一节的输出日志,我们可以看到已经没有连接泄漏的现象了。一个执行线程在运行JdbcUserService#logon()方法时,只占用一个连接,而且方法执行完毕后,该连接马上释放。这说明通过DataSourceUtils.getConnection()方法确实获取了方法所在事务上下文绑定的那个连接,而不是像原来那样从数据源中获取一个新的连接。
通过DataSourceUtils获取数据连接
是否使用DataSourceUtils获取数据连接就可以高枕无忧了呢?理想很美好,但现实很残酷:如果DataSourceUtils在没有事务上下文的方法中使用getConnection()获取连接,依然会造成数据连接泄漏!
保持上面的代码不变,将上面Spring配置文件中①处的Spring AOP事务增强配置的代码注释掉,重新运行代码清单10-23的代码,将得到如下的输出日志:
引用
连接数[active:idle]-[0:0]
连接数[active:idle]-[1:1]
连接数[active:idle]-[1:1]
连接数[active:idle]-[2:1]
连接数[active:idle]-[2:1]
连接数[active:idle]-[1:1]
连接数[active:idle]-[1:1]
连接数[active:idle]-[2:1]
连接数[active:idle]-[2:1]
我们通过下表对数据源连接的占用和泄漏情况进行描述。
仔细对上表的执行过程,我们发现在T1时,有事务上下文时的active为2,idle为0,而此时由于没有事务管理,则active为1而idle也为1。这说明有事务上下文时,需要等到整个事务方法(即logon())返回后,事务上下文绑定的连接才被释放。但在没有事务上下文时,logon()调用JdbcTemplate执行完数据操作后,马上就释放连接。
时间 | 执行线程1 | 执行线程2 | 数据源连接 | ||
active | idle | leak | |||
T0 | 未启动 | 未启动 | 0 | 0 | 0 |
T1 | 正在执行方法 | 未启动 | 1 | 1 | 0 |
T2 | 执行完毕 | 未启动 | 1 | 1 | 1 |
T3 | 执行完毕 | 正式执行方法 | 2 | 1 | 1 |
T4 | 执行完毕 | 执行完毕 | 2 | 1 | 2 |
在T2执行线程完成logon()方法的调用后,有一个连接没有被释放(active),所以发生了连接泄漏。到T4时,两个执行线程都完成了logon()方法的调用,但是出现了两个未释放的连接。
要堵上这个连接泄漏的漏洞,需要对logon()方法进行如下的改造:
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()); } } }
在②处显式调用DataSourceUtils.releaseConnection()方法释放获取的连接。特别需要指出的是:一定不能在①处释放连接!因为如果logon()在获取连接后,①处代码前这段代码执行时发生异常,则①处释放连接的动作将得不到执行。这将是一个非常具有隐蔽性的连接泄漏的隐患点。
JdbcTemplate如何做到对连接泄漏的免疫
分析JdbcTemplate的代码,我们可以清楚地看到它开放的每个数据操作方法,首先都使用DataSourceUtils获取连接,在方法返回之前使用DataSourceUtils释放连接。
来看一下JdbcTemplate最核心的一个数据操作方法execute():
public <T> T execute(StatementCallback<T> action) throws DataAccessException { //①首先根据DataSourceUtils获取数据连接 Connection con = DataSourceUtils.getConnection(getDataSource()); Statement stmt = null; try { Connection conToUse = con; … handleWarnings(stmt); return result; } catch (SQLException ex) { JdbcUtils.closeStatement(stmt); stmt = null; //②发生异常时,使用DataSourceUtils释放数据连接 DataSourceUtils.releaseConnection(con, getDataSource()); con = null; throw getExceptionTranslator().translate( "StatementCallback", getSql(action), ex); } finally { JdbcUtils.closeStatement(stmt); //③最后再使用DataSourceUtils释放数据连接 DataSourceUtils.releaseConnection(con, getDataSource()); } }
在①处通过DataSourceUtils.getConnection()获取连接,在②和③处通过DataSourceUtils.releaseConnection()释放连接。所有JdbcTemplate开放的数据访问API最终都是直接或间接由execute(StatementCallback<T> action)方法执行数据访问操作的,因此这个方法代表了JdbcTemplate数据操作的最终实现方式。
正是因为JdbcTemplate严谨的获取连接及释放连接的模式化流程保证了JdbcTemplate对数据连接泄漏问题的免疫性。所以,如有可能尽量使用JdbcTemplate、HibernateTemplate等这些模板进行数据访问操作,避免直接获取数据连接的操作。
使用TransactionAwareDataSourceProxy
如果不得已要显式获取数据连接,除了使用DataSourceUtils获取事务上下文绑定的连接外,还可以通过TransactionAwareDataSourceProxy对数据源进行代理。数据源对象被代理后就具有了事务上下文感知的能力,通过代理数据源的getConnection()方法获取连接和使用DataSourceUtils.getConnection()获取连接的效果是一样的。
下面是使用TransactionAwareDataSourceProxy对数据源进行代理的配置:
<?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"/> <!--①未被代理的数据源 --> <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}"/> <!--②对数据源进行代码,使数据源具体事务上下文感知性 --> <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>
对数据源进行代理后,我们就可以通过数据源代理对象的getConnection()获取事务上下文中绑定的数据连接了。因此,如果数据源已经进行了 TransactionAwareDataSourceProxy的代理,而且方法存在事务上下文,那么代码清单10-19的代码也不会生产连接泄漏的问题。
其他数据访问技术的等价类
理解了Spring JDBC的数据连接泄漏问题,其中的道理可以平滑地推广到其他框架中去。Spring为每个数据访问技术框架都提供了一个获取事务上下文绑定的数据连接(或其衍生品)的工具类和数据源(或其衍生品)的代理类。
表10-5列出了不同数据访问技术对应DataSourceUtils的等价类。
表10-5 不同数据访问框架DataSourceUtils的等价类
数据访问技术框架 | 连接(或衍生品)获取工具类 |
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列出了不同数据访问技术框架下TransactionAwareDataSourceProxy的等价类。
表10-6 不同数据访问框架TransactionAwareDataSourceProxy的等价类
数据访问技术框架 | 连接(或衍生品)获取工具类 |
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 |
注:以上内容摘自《Spring 4.x企业应用开发实战》
评论
2 楼
huang_yong
2012-04-14
写程序的时候,如果是从底层获取Connection,一定要注意在finally里面close掉,但是如果从Hibernate的SessionFactory级别获取Connection,就无需手工close掉,这些工作都由Hibernate为我们完成了。
一定要使用:
Connection conn = SessionFactoryUtils.getDataSource(hibernateTemplate.getSessionFactory()).getConnection();
获取Connection,以上代码可以进行封装,放入公共类中,方便其他程序员使用。
一定要使用:
Connection conn = SessionFactoryUtils.getDataSource(hibernateTemplate.getSessionFactory()).getConnection();
获取Connection,以上代码可以进行封装,放入公共类中,方便其他程序员使用。
1 楼
jinnianshilongnian
2012-03-09
1、打开连接不关闭 这是程序员犯的不该犯的错误(发现错误后可以改)
2、当有事务方法 特别慢,会拖慢整个应用 甚至造成死锁。
以前我们用c3p0曾经遇到过类似的,在用户注册高峰时,由于赠送积分/还有一些道具之类的是和另一个系统集成的,所以在高峰期特别慢,从而导致整个注册方法特别慢,最后改成异步赠送(失败了影响也是比较小的)。
因此第一种情况,是可以查找并改正的。
第二种情况,需要实际情况实际分析。
2、当有事务方法 特别慢,会拖慢整个应用 甚至造成死锁。
以前我们用c3p0曾经遇到过类似的,在用户注册高峰时,由于赠送积分/还有一些道具之类的是和另一个系统集成的,所以在高峰期特别慢,从而导致整个注册方法特别慢,最后改成异步赠送(失败了影响也是比较小的)。
因此第一种情况,是可以查找并改正的。
第二种情况,需要实际情况实际分析。
发表评论
-
一个常见的Spring IOC疑难症状
2013-07-25 14:14 5085Case 请看下面的IOC实例: 1)Aa ... -
mybatis3.1分页自动添加总数
2013-07-08 21:11 22885问题 1.mybatis默认分页是内存分页的,谁用谁崩溃啊! ... -
Rop开发手册(1):最简单的服务开放平台框架
2012-08-08 11:35 8686Rop概述 Rop是Rapid Open Pl ... -
学习Spring必学的Java基础知识(9)----HTTP请求报文
2012-06-09 16:02 13993引述要学习Spring框架的技术内幕,必须事先掌握一些基本的J ... -
学习Spring必学的Java基础知识(8)----国际化信息
2012-05-26 11:19 28556引述要学习Spring框架的 ... -
学习Spring必学的Java基础知识(7)----事务基础知识
2012-05-26 10:57 5097引述要学习Spring框架的技术内幕,必须事先掌握一些基本的J ... -
学习Spring必学的Java基础知识(6)----ThreadLocal
2012-05-19 10:09 12171引述要学习Spring框架的 ... -
学习Spring必学的Java基础知识(5)----注解
2012-05-19 09:56 5783引述要学习Spring框架的技术内幕,必须事先掌握一些基本的J ... -
学习Spring必学的Java基础知识(4)----XML基础知识
2012-05-12 15:33 8570引述要学习Spring框架的 ... -
学习Spring必学的Java基础知识(3)----PropertyEditor
2012-05-12 15:13 16877引述要学习Spring框架的 ... -
明明白白AOP(傻瓜也会心领神会!)
2012-05-05 11:04 10626引子: AOP(面向方面编 ... -
学习Spring必学的Java基础知识(2)----动态代理
2012-05-02 13:03 9745引述要学习Spring框架的 ... -
学习Spring必学的Java基础知识(1)----反射
2012-04-25 13:57 89911引述要学习Spring框架的技术内幕,必须事先掌握一些基本的J ... -
透透彻彻IoC(你没有理由不懂!)
2012-04-18 11:01 94251引述:IoC(控制反转:I ... -
单元测试系列之5:使用unitils测试Service层
2012-04-14 10:48 18497引述:Spring 的测试框架为我们提供一个强大的测试环境,解 ... -
如何用Spring读取JAR中的文件
2012-04-13 17:22 18438使用如下方式读取JAR中的文件出错 类路径下 ... -
单元测试系列之4:使用Unitils测试DAO层
2012-04-12 16:32 19702Spring 的测试框架为我们提供一个强大的测试环境,解 ... -
单元测试系列之3:测试整合之王Unitils
2012-04-09 14:11 15711引述:程序测试对保障应用程序正确性而言,其重要性怎么样强调都不 ... -
单元测试系列之2:模拟利器Mockito
2012-03-30 11:38 15350引述:程序测试对 ... -
单元测试系列之1:开发测试的那些事儿
2012-03-28 12:52 10034引述:程序测试对保障应用程序正确性而言,其重要性怎 ...
相关推荐
修炼成Javascript中级程序员必知必会_资源分享
内容概要:本文详细介绍了如何使用MATLAB的深度学习工具箱,在果树病虫害识别任务中从数据准备、模型设计、训练优化到最后的模型评估与应用全流程的具体实施步骤和技术要点。涵盖了MATLAB深度学习工具箱的基本概念及其提供的多种功能组件,如卷积神经网络(CNN)的应用实例。此外,文中还具体讲述了数据集的收集与预处理方法、不同类型的深度学习模型搭建、训练过程中的超参数设定及其优化手段,并提供了病虫害识别的实际案例。最后展望了深度学习技术在未来农业领域的潜在影响力和发展前景。 适合人群:对深度学习及农业应用感兴趣的科研人员、高校师生和相关从业者。 使用场景及目标:①希望掌握MATLAB环境下构建深度学习模型的方法和技术细节;②从事果树病虫害管理研究或实践,寻找高效的自动化解决方案。 阅读建议:在阅读本文之前,建议读者熟悉基本的MATLAB编程环境及初步了解机器学习的相关概念。针对文中涉及的理论和技术难点,可以通过官方文档或其他教程进行补充学习。同时,建议动手实践每一个关键点的内容,在实践中加深理解和掌握技能。
nodejs010-nodejs-block-stream-0.0.7-1.el6.centos.alt.noarch.rpm
机械模型与技术交底书的融合:创新点详解与解析,机械模型加技术交底书,有创新点 ,机械模型; 技术交底书; 创新点,创新机械模型与技术交底书详解
免费JAVA毕业设计 2024成品源码+论文+数据库+启动教程 启动教程:https://www.bilibili.com/video/BV1SzbFe7EGZ 项目讲解视频:https://www.bilibili.com/video/BV1Tb421n72S 二次开发教程:https://www.bilibili.com/video/BV18i421i7Dx
免费JAVA毕业设计 2024成品源码+论文+数据库+启动教程 启动教程:https://www.bilibili.com/video/BV1SzbFe7EGZ 项目讲解视频:https://www.bilibili.com/video/BV1Tb421n72S 二次开发教程:https://www.bilibili.com/video/BV18i421i7Dx
nodejs010-nodejs-cmd-shim-1.1.0-4.1.el6.centos.alt.noarch.rpm
西门子四轴卧加后处理系统:828D至840D兼容,四轴联动高效加工解决方案,支持图档处理及试看程序。,西门子四轴卧加后处理,支持828D~840D系统,支持四轴联动,可制制,看清楚联系,可提供图档处理试看程序 ,核心关键词:西门子四轴卧加后处理; 828D~840D系统支持; 四轴联动; 制程; 联系; 图档处理试看程序。,西门子四轴卧加后处理程序,支持多种系统与四轴联动
基于黏菌优化算法(SMA)的改进与复现——融合EO算法更新策略的ESMA项目报告,黏菌优化算法(SMA)复现(融合EO算法改进更新策略)——ESMA。 复现内容包括:改进算法实现、23个基准测试函数、多次实验运行并计算均值标准差等统计量、与SMA对比等。 程序基本上每一步都有注释,非常易懂,代码质量极高,便于新手学习和理解。 ,SMA复现;EO算法改进;算法实现;基准测试函数;实验运行;统计量;SMA对比;程序注释;代码质量;学习理解。,标题:ESMA算法复现:黏菌优化与EO算法融合改进的实证研究
基于MATLAB的Stewart平台并联机器人仿真技术研究与实现:Simscape环境下的虚拟模拟分析与应用,MATLAB并联机器人Stewart平台仿真simscape ,MATLAB; 并联机器人; Stewart平台; 仿真; Simscape; 关键技术。,MATLAB中Stewart平台并联机器人Simscape仿真
Grad-CAM可视化医学3D影像
探索comsol泰勒锥:电流体动力学的微观世界之旅,comsol泰勒锥、电流体动力学 ,comsol泰勒锥; 电流体动力学; 锥形结构; 电场影响,COMSOL泰勒锥与电流体动力学研究
免费JAVA毕业设计 2024成品源码+论文+数据库+启动教程 启动教程:https://www.bilibili.com/video/BV1SzbFe7EGZ 项目讲解视频:https://www.bilibili.com/video/BV1Tb421n72S 二次开发教程:https://www.bilibili.com/video/BV18i421i7Dx
PFC6.03D模型动态压缩模拟与SHPB霍普金森压杆系统理论及实验数据处理技术解析,PFC6.03D模型,动态压缩模拟,还包括: SHPB霍普金森压杆系统理论知识介绍,二波法和三波法处理实验数据,提出三波波形,计算动态压缩强度等 ,PFC模型; 动态压缩模拟; SHPB霍普金森压杆系统; 理论介绍; 二波法处理; 三波法处理; 三波波形; 动态压缩强度。,"PFC模型下的动态压缩模拟及SHPB理论实践研究"
ProASCI 开发板原理图,适用于A3P3000
免费JAVA毕业设计 2024成品源码+论文+录屏+启动教程 启动教程:https://www.bilibili.com/video/BV1SzbFe7EGZ 项目讲解视频:https://www.bilibili.com/video/BV1Tb421n72S 二次开发教程:https://www.bilibili.com/video/BV18i421i7Dx
1、文件内容:pykde4-devel-4.10.5-6.el7.rpm以及相关依赖 2、文件形式:tar.gz压缩包 3、安装指令: #Step1、解压 tar -zxvf /mnt/data/output/pykde4-devel-4.10.5-6.el7.tar.gz #Step2、进入解压后的目录,执行安装 sudo rpm -ivh *.rpm 4、安装指导:私信博主,全程指导安装
基于Comsol模拟的三层顶板随机裂隙浆液扩散模型:考虑重力影响的瞬态扩散规律分析,Comsol模拟,考虑三层顶板包含随机裂隙的浆液扩散模型,考虑浆液重力的影响,模型采用的DFN插件建立随机裂隙,采用达西定律模块中的储水模型为控制方程,分析不同注浆压力条件下的浆液扩散规律,建立瞬态模型 ,Comsol模拟; 随机裂隙浆液扩散模型; 浆液重力影响; DFN插件; 达西定律模块储水模型; 注浆压力条件; 浆液扩散规律; 瞬态模型,Comsol浆液扩散模型:随机裂隙下考虑重力的瞬态扩散分析
A simple fast, easy use distributed file system written by golang(similar fastdfs).go-fastdfs
手机编程-1738391552157.jpg