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

2014-11-24 08:51:31 · 作者: · 浏览: 0

对于应用开发者来说,数据连接泄漏无疑是一个可怕的梦魇。只要你开发的应用存在数据连接泄漏的问题,应用程序最终都将因数据连接资源的耗尽而崩溃,甚至还可能引起数据库的崩溃。数据连接泄漏像一个黑洞那样让开发者避之唯恐不及。
Spring DAO对所有支持的数据访问技术框架都使用模板化技术进行了薄层的封装。只要你的程序都使用Spring DAO的模板(如JdbcTemplate、HibernateTemplate等)进行数据访问,一定不会存在数据连接泄漏的问题——这是Spring 给予我们的郑重承诺!如果使用Spring DAO模板进行数据操作,我们无须关注数据连接(Connection)及其衍生品(Hibernate的Session等)的获取和释放操作,模板类已 经通过其内部流程替我们完成了,且对开发者是透明的。
但是由于集成第三方产品、整合遗产代码等原因,可能需要直接访问数据源或直接获取数据连接及其衍生品。这时,如果使用不当,就可能在无意中创造出一个魔鬼般的连接泄漏问题。
我们知道:当Spring事务方法运行时,就产生一个事务上下文,该上下文在本事务执行线程中针对同一个数据源绑定了一个唯一的数据连接(或其衍 生品),所有被该事务上下文传播的方法都共享这个数据连接。这个数据连接从数据源获取及返回给数据源都在Spring掌控之中,不会发生问题。如果在需要 数据连接时,能够获取这个被Spring管控的数据连接,则使用者可以放心使用,无须关注连接释放的问题。
那么,如何获取这些被Spring管控的数据连接呢?Spring提供了两种方法:其一是使用数据资源获取工具类;其二是对数据源(或其衍生品如Hibernate SessionFactory)进行代理。

Spring JDBC数据连接泄漏


如果我们从数据源直接获取连接,且在使用完成后不主动归还给数据源(调用Connection#close()),则将造成数据连接泄漏的问题。

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

12 //①直接从数据源获取连接,后续程序没有显式释放该连接

13 Connection conn = jdbcTemplate.getDataSource().getConnection();

14 String sql = "UPDATE t_user SET last_logon_time= WHERE user_name = ";

15 jdbcTemplate.update(sql, System.currentTimeMillis(), userName);

16

17 //②模拟程序代码的执行时间

18 Thread.sleep(1000);

19 } catch (Exception e) {

20 e.printStackTrace();

21 }

22 }

23 }

JdbcUserService通过Spring AOP事务增强的配置,让所有public方法都工作在事务环境中,即让logon()和updateLastLogonTime()方法拥有事务功能。 在logon()方法内部,我们在①处通过调用jdbcTemplate.getDataSource().getConnection()显式获取一个 连接,这个连接不是logon()方法事务上下文线程绑定的连接,所以如果开发者没有手工释放这个连接(显式调用Connection#close()方 法),则这个连接将永久被占用(处于active状态),造成连接泄漏!下面,我们编写模拟运行的代码,查看方法执行对数据连接的实际占用情况:

01 package com.baobaotao.connleak;

02 …

03 @Service("jdbcUserService")

04 public class JdbcUserService {

05 …

06 //①以异步线程的方式执行JdbcUserService#logon()方法,以模拟多线程的环境

07 public static void asynchrLogon(JdbcUserService userService, String userName) {

08 UserServiceRunner runner = new UserServiceRunner(userService, userName);

09 runner.start();

10 }

11 private static class UserServiceRunner extends Thread {

12 private JdbcUserService userService;

13 private String userName;

14 public UserServiceRunner(JdbcUserService userService, String userName) {

15 this.userService = userService;

16 this.userName = userName;

17 }

18 public void run() {

19 userService.logon(userName);

20 }

21 }

22

23 //②让主执行线程睡眠一段指定的时间

24 public static void sleep(long time) {

25 try {

26 Thread.sleep(time);

27 } catch (InterruptedException e) {

28 e.printStackTrace();

29 }

30 }

31

32 //③汇报数据源的连接占用情况

33 public static void reportConn(BasicDataSource basicDataSource) {

34 System.out.println("连接数[active:idle]-[" +

35 basicDataSource.getNumA