Spring的事务管理难点剖析(7):数据连接泄漏(五)

2014-11-24 08:51:31 · 作者: · 浏览: 10
: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]


我们通过下表对数据源连接的占用和泄漏情况进行描述。
仔细对上表的执行过程,我们发现在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 T execute(StatementCallback action) throws DataAccessException {

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