- 浏览: 1528921 次
- 性别:
- 来自: 厦门
博客专栏
-
Spring 3.x企业实...
浏览量:464645
文章分类
最新评论
-
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 5072Case 请看下面的IOC实例: 1)Aa ... -
mybatis3.1分页自动添加总数
2013-07-08 21:11 22867问题 1.mybatis默认分页是内存分页的,谁用谁崩溃啊! ... -
Rop开发手册(1):最简单的服务开放平台框架
2012-08-08 11:35 8637Rop概述 Rop是Rapid Open Pl ... -
学习Spring必学的Java基础知识(9)----HTTP请求报文
2012-06-09 16:02 13959引述要学习Spring框架的技术内幕,必须事先掌握一些基本的J ... -
学习Spring必学的Java基础知识(8)----国际化信息
2012-05-26 11:19 28499引述要学习Spring框架的 ... -
学习Spring必学的Java基础知识(7)----事务基础知识
2012-05-26 10:57 5093引述要学习Spring框架的技术内幕,必须事先掌握一些基本的J ... -
学习Spring必学的Java基础知识(6)----ThreadLocal
2012-05-19 10:09 12156引述要学习Spring框架的 ... -
学习Spring必学的Java基础知识(5)----注解
2012-05-19 09:56 5780引述要学习Spring框架的技术内幕,必须事先掌握一些基本的J ... -
学习Spring必学的Java基础知识(4)----XML基础知识
2012-05-12 15:33 8563引述要学习Spring框架的 ... -
学习Spring必学的Java基础知识(3)----PropertyEditor
2012-05-12 15:13 16859引述要学习Spring框架的 ... -
明明白白AOP(傻瓜也会心领神会!)
2012-05-05 11:04 10606引子: AOP(面向方面编 ... -
学习Spring必学的Java基础知识(2)----动态代理
2012-05-02 13:03 9733引述要学习Spring框架的 ... -
学习Spring必学的Java基础知识(1)----反射
2012-04-25 13:57 89841引述要学习Spring框架的技术内幕,必须事先掌握一些基本的J ... -
透透彻彻IoC(你没有理由不懂!)
2012-04-18 11:01 94235引述:IoC(控制反转:I ... -
单元测试系列之5:使用unitils测试Service层
2012-04-14 10:48 18481引述:Spring 的测试框架为我们提供一个强大的测试环境,解 ... -
如何用Spring读取JAR中的文件
2012-04-13 17:22 18411使用如下方式读取JAR中的文件出错 类路径下 ... -
单元测试系列之4:使用Unitils测试DAO层
2012-04-12 16:32 19692Spring 的测试框架为我们提供一个强大的测试环境,解 ... -
单元测试系列之3:测试整合之王Unitils
2012-04-09 14:11 15694引述:程序测试对保障应用程序正确性而言,其重要性怎么样强调都不 ... -
单元测试系列之2:模拟利器Mockito
2012-03-30 11:38 15333引述:程序测试对 ... -
单元测试系列之1:开发测试的那些事儿
2012-03-28 12:52 10024引述:程序测试对保障应用程序正确性而言,其重要性怎 ...
相关推荐
第10章 Spring的事务管理难点剖析 10.1 DAO和事务管理的牵绊 10.1.1 JDBC访问数据库 10.1.2 Hibernate访问数据库 10.2 应用分层的迷惑 10.3 事务方法嵌套调用的迷茫 10.3.1 Spring事务传播机制回顾 10.3.2 相互嵌套...
第10章 Spring的事务管理难点剖析 10.1 DAO和事务管理的牵绊 10.1.1 JDBC访问数据库 10.1.2 Hibernate访问数据库 10.2 应用分层的迷惑 10.3 事务方法嵌套调用的迷茫 10.3.1 Spring事务传播机制回顾 10.3.2 相互嵌套...
- **解决方案**:利用事务管理机制确保数据的一致性,通过数据库约束保证数据完整性。 - **难点二**:如何提高系统的并发处理能力? - **解决方案**:优化查询语句,使用缓存技术减少数据库访问频率,合理配置...
学生成绩管理系统的核心目标是自动化处理与成绩相关的事务,包括录入、查询、统计和分析学生分数。它减少了手动操作的繁琐,提高了工作效率,同时也确保了数据的准确性和一致性。系统通常包含以下主要模块: 1. ...
- **数据一致性**:通过事务管理和数据库锁机制保证数据的一致性和完整性。 - **性能优化**:采用缓存技术和异步处理等方式提高系统响应速度。 - **安全性**:采用SSL加密传输、SQL注入预防等措施加强系统安全性。 ...
8. **表格**:“表.docx”可能包含系统的数据统计表格、需求分析表、任务分配表等,帮助管理和组织项目进程。 总的来说,《酒店管理系统的设计与实现》这个项目不仅展示了软件开发的全生命周期,还涵盖了多种IT技术...
SSH(Spring、Struts2、Hibernate)是一个常见的Java Web开发框架,它提供了模型、视图和控制器的分离,以及数据持久化、事务管理等功能。 3.2 JSP JSP(JavaServer Pages)用于动态生成网页,结合Servlet技术,...
11. **数据库知识**:SQL基本操作,事务管理,JDBC,连接池(C3P0, HikariCP等),以及NoSQL数据库如MongoDB的基本概念。 12. **网络编程**:TCP/IP协议基础,Socket编程,HTTP协议,RESTful API设计。 13. **并发...
7. **软件框架和工具**:根据ACCP课程,可能会考察Java相关的Spring框架、Maven构建工具,或者前端的React、Vue.js等。面试者需要展示对这些工具的熟练使用和理解。 8. **问题解决能力**:面试官可能会给出一些实际...
面试中可能会要求分析内存泄漏或性能瓶颈,或者让你解释各种GC算法的工作原理。 Spring框架作为Java企业级应用的主流选择,其核心概念如依赖注入(DI)、AOP(面向切面编程)、Bean生命周期管理等都是面试热点。...
数据库操作是大多数互联网应用的基础,因此SQL语句的编写和优化、事务管理、索引原理、数据库设计原则等内容也是面试中不可或缺的部分。尤其在大数据量的背景下,如何进行高效的查询和存储是考察的重点。 框架和...
理解堆栈内存的区别,知道如何进行对象引用,以及分析内存泄漏和性能优化,这些都是面试中可能被问到的问题。 Java多线程是另一个难点,包括线程的创建、同步机制(synchronized、Lock、Semaphore等)、线程池的...