连接数[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]
我们通过下表对数据源连接的占用和泄漏情况进行描述。
仔细对上表的执行过程,我们发现在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()方法进行如下的改造:
01 package com.baobaotao.connleak;
02 …
03 @Service("jdbcUserService")
04 public class JdbcUserService {
05 @Autowired
06 private JdbcTemplate jdbcTemplate;
07
08 @Transactional
09 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 }
在②处显式调用DataSourceUtils.releaseConnection()方法释放获取的连接。特别需要指出的是:一定不能在①处释放连 接!因为如果logon()在获取连接后,①处代码前这段代码执行时发生异常,则①处释放连接的动作将得不到执行。这将是一个非常具有隐蔽性的连接泄漏的 隐患点。
JdbcTemplate如何做到对连接泄漏的免疫
分析JdbcTemplate的代码,我们可以清楚地看到它开放的每个数据操作方法,首先都使用DataSourceUtils获取连接,在方法返回之前使用DataSourceUtils释放连接。
来看一下JdbcTemplate最核心的一个数据操作方法execute():
01 public
02
03 //①首先根据DataSourceUtils获取数据连接
04 Connection con = DataSourceUtils.getConnection(getDataSource());
05 Statement stmt = null;
06 try {
07 Connection conToUse = con;
08 …
09 handleWarnings(stmt);
10 return result;
11 }
12 catch (SQLException ex) {
13 JdbcUtils.closeStatement(stmt);
14 stmt = null;
15
16 //②发生异常时,使用DataSourceUtils释放数据连接
17 DataSourceUtils.releaseConnection(con, getDataSource());
18 con = null;
19 throw getExceptionTranslator().translate(
20 "StatementCallback", getSql(action), ex);
21 }
22 finally {
23 JdbcUtils.closeStatement