`

spring 事务管理难点解析:早上一起来就研究

 
阅读更多

------------------------------------------------------------------------------------------------------    
   引述:Spring给我们带来的最大的一个便利是透明地实现事务管理,但是一般开发者仅仅是了解如何配置,在实际应用过程中却往往遇到很多问题,存在很多困惑。有有鉴于此,我特意总结了各种实际应用中遇到的Spring事务管理的困惑,这些文章摘自于我的《Spring 3.x企业应用开发实战》的第10章,我将通过连载的方式,陆续在此发出。欢迎大家讨论。
------------------------------------------------------------------------------------------------------

底层连接资源的访问问题

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

Spring JDBC数据连接泄漏

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

Java代码 复制代码 收藏代码
  1. package com.baobaotao.connleak;   
  2. …   
  3. @Service("jdbcUserService")   
  4. public class JdbcUserService {   
  5.     @Autowired  
  6.     private JdbcTemplate jdbcTemplate;   
  7.   
  8.     @Transactional  
  9.     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. }  
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状态),造成连接泄漏!下面,我们编写模拟运行的代码,查看方法执行对数据连接的实际占用情况:

Java代码 复制代码 收藏代码
  1. package com.baobaotao.connleak;   
  2. …   
  3. @Service("jdbcUserService")   
  4. public class JdbcUserService {   
  5.     …   
  6.     //①以异步线程的方式执行JdbcUserService#logon()方法,以模拟多线程的环境   
  7.     public static void asynchrLogon(JdbcUserService userService, String userName) {   
  8.         UserServiceRunner runner = new UserServiceRunner(userService, userName);   
  9.         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.getNumActive()+":"+basicDataSource.getNumIdle()+"]");   
  36.     }   
  37.   
  38.     public static void main(String[] args) {   
  39.         ApplicationContext ctx =    
  40.          new ClassPathXmlApplicationContext("com/baobaotao/connleak/applicatonContext.xml");   
  41.         JdbcUserService userService = (JdbcUserService) ctx.getBean("jdbcUserService");   
  42.   
  43.         BasicDataSource basicDataSource = (BasicDataSource) ctx.getBean("dataSource");   
  44.            
  45.         //④汇报数据源初始连接占用情况   
  46.         JdbcUserService.reportConn(basicDataSource);   
  47.   
  48.         JdbcUserService.asynchrLogon(userService, "tom");//启动一个异常线程A   
  49.         JdbcUserService.sleep(500);   
  50.   
  51.         //⑤此时线程A正在执行JdbcUserService#logon()方法   
  52.         JdbcUserService.reportConn(basicDataSource);    
  53.   
  54.         JdbcUserService.sleep(2000);   
  55.   
  56.         //⑥此时线程A所执行的JdbcUserService#logon()方法已经执行完毕   
  57.         JdbcUserService.reportConn(basicDataSource);   
  58.   
  59.         JdbcUserService.asynchrLogon(userService, "john");//启动一个异常线程B   
  60.         JdbcUserService.sleep(500);   
  61.   
  62.         //⑦此时线程B正在执行JdbcUserService#logon()方法   
  63.         JdbcUserService.reportConn(basicDataSource);   
  64.            
  65.         JdbcUserService.sleep(2000);   
  66.   
  67.         //⑧此时线程A和B都已完成JdbcUserService#logon()方法的执行   
  68.         JdbcUserService.reportConn(basicDataSource);   
  69.   
  70.     }  
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代码 复制代码 收藏代码
  1. <?xml version="1.0" encoding="UTF-8" ?>  
  2. <beans xmlns="http://www.springframework.org/schema/beans"  
  3.       …   
  4.      http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">  
  5.     <context:component-scan base-package="com.baobaotao.connleak"/>  
  6.     <context:property-placeholder location="classpath:jdbc.properties"/>  
  7.     <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"  
  8.         destroy-method="close"    
  9.         p:driverClassName="${jdbc.driverClassName}"  
  10.         p:url="${jdbc.url}"    
  11.         p:username="${jdbc.username}"  
  12.         p:password="${jdbc.password}"/>  
  13.   
  14.     <bean id="jdbcTemplate"  
  15.           class="org.springframework.jdbc.core.JdbcTemplate"  
  16.           p:dataSource-ref="dataSource"/>  
  17.   
  18.     <bean id="transactionManager"  
  19.           class="org.springframework.jdbc.datasource.DataSourceTransactionManager"  
  20.           p:dataSource-ref="dataSource"/>  
  21.   
  22.      <!--①启用注解驱动的事务增强-->  
  23.      <tx:annotation-driven/>         
  24. </beans>  
<?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]


我们通过表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从数据源获取连接的关键代码:

Java代码 复制代码 收藏代码
  1. public abstract class DataSourceUtils {   
  2. …   
  3. public static Connection doGetConnection(DataSource dataSource) throws SQLException {   
  4.         Assert.notNull(dataSource, "No DataSource specified");   
  5.   
  6.      //①首先尝试从事务同步管理器中获取数据连接   
  7.     ConnectionHolder conHolder =    
  8.            (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);   
  9.     if (conHolder != null && (conHolder.hasConnection() ||    
  10.                           conHolder.isSynchronizedWithTransaction())) {    
  11.             conHolder.requested();   
  12.             if (!conHolder.hasConnection()) {   
  13.                 logger.debug("Fetching resumed JDBC Connection from DataSource");   
  14.                 conHolder.setConnection(dataSource.getConnection());   
  15.             }   
  16.             return conHolder.getConnection();   
  17.         }   
  18.   
  19.           //②如果获取不到连接,则直接从数据源中获取连接   
  20.         Connection con = dataSource.getConnection();   
  21.   
  22.          //③如果拥有事务上下文,则将连接绑定到事务上下文中   
  23.         if (TransactionSynchronizationManager.isSynchronizationActive()) {   
  24.               ConnectionHolder holderToUse = conHolder;   
  25.             if (holderToUse == null) {   
  26.                 holderToUse = new ConnectionHolder(con);   
  27.             }   
  28.             else {holderToUse.setConnection(con);}   
  29.             holderToUse.requested();   
  30.             TransactionSynchronizationManager.registerSynchronization(   
  31.                               new ConnectionSynchronization(holderToUse, dataSource));   
  32.             holderToUse.setSynchronizedWithTransaction(true);   
  33.             if (holderToUse != conHolder) {   
  34.                 TransactionSynchronizationManager.bindResource(   
  35.                                                             dataSource, holderToUse);   
  36.             }   
  37.         }   
  38.         return con;   
  39.     }   
  40.      …   
  41. }  
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()替换直接从数据源中获取连接的代码:

Java代码 复制代码 收藏代码
  1. package com.baobaotao.connleak;   
  2. …   
  3. @Service("jdbcUserService")   
  4. public class JdbcUserService {   
  5.     @Autowired  
  6.     private JdbcTemplate jdbcTemplate;   
  7.   
  8.     @Transactional  
  9.     public void logon(String userName) {   
  10.         try {   
  11.             //①使用DataSourceUtils获取数据连接   
  12.           Connection conn =    
  13.                  DataSourceUtils.getConnection(jdbcTemplate.getDataSource());   
  14.             //Connection conn = jdbcTemplate.getDataSource().getConnection();   
  15.   
  16.             String sql = "UPDATE t_user SET last_logon_time=? WHERE user_name =?";   
  17.             jdbcTemplate.update(sql, System.currentTimeMillis(), userName);   
  18.             Thread.sleep(1000);    
  19.         } catch (Exception e) {   
  20.             e.printStackTrace();   
  21.         }   
  22.     }   
  23. }  
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]



   对照上一节的输出日志,我们可以看到已经没有连接泄漏的现象了。一个执行线程在运行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()方法进行如下的改造:

Java代码 复制代码 收藏代码
  1. package com.baobaotao.connleak;   
  2. …   
  3. @Service("jdbcUserService")   
  4. public class JdbcUserService {   
  5.     @Autowired  
  6.     private JdbcTemplate jdbcTemplate;   
  7.   
  8.     @Transactional  
  9.     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. }  
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():

Java代码 复制代码 收藏代码
  1. public <T> T execute(StatementCallback<T> action) throws DataAccessException {   
  2.   
  3.         //①首先根据DataSourceUtils获取数据连接   
  4.          Connection con = DataSourceUtils.getConnection(getDataSource());   
  5.         Statement stmt = null;   
  6.         try {   
  7.             Connection conToUse = con;   
  8.             …   
  9.             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(stmt);   
  24.                  
  25.               //③最后再使用DataSourceUtils释放数据连接   
  26.             DataSourceUtils.releaseConnection(con, getDataSource());   
  27.         }   
  28.     }  
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代码 复制代码 收藏代码
  1. <?xml version="1.0" encoding="UTF-8" ?>  
  2. <beans xmlns="http://www.springframework.org/schema/beans"  
  3.        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  4.        …   
  5.        http://www.springframework.org/schema/tx       
  6.        http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">  
  7.     <context:component-scan base-package="com.baobaotao.connleak"/>  
  8.     <context:property-placeholder location="classpath:jdbc.properties"/>  
  9.   
  10.     <!--①未被代理的数据源 -->  
  11.     <bean id="originDataSource" class="org.apache.commons.dbcp.BasicDataSource"  
  12.         destroy-method="close"    
  13.         p:driverClassName="${jdbc.driverClassName}"  
  14.         p:url="${jdbc.url}"    
  15.         p:username="${jdbc.username}"  
  16.         p:password="${jdbc.password}"/>  
  17.        
  18.      <!--②对数据源进行代码,使数据源具体事务上下文感知性 -->  
  19.     <bean id="dataSource"  
  20.         class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy"  
  21.         p:targetDataSource-ref="originDataSource" />  
  22.   
  23.     <bean id="jdbcTemplate"  
  24.           class="org.springframework.jdbc.core.JdbcTemplate"  
  25.           p:dataSource-ref="dataSource"/>  
  26.   
  27.     <bean id="transactionManager"  
  28.           class="org.springframework.jdbc.datasource.DataSourceTransactionManager"  
  29.           p:dataSource-ref="dataSource"/>            
  30.     <tx:annotation-driven/>         
  31. </beans>  
<?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 3.x企业应用开发实战》

 

访问:http://stamen.iteye.com/

数据访问技术框架 连接(或衍生品)获取工具类                                                    
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事务管理Demo

    在Spring框架中,事务管理是核心特性之一,它允许开发者以声明式或编程式的方式处理应用中的事务。Spring事务管理的目的是确保数据的一致性和完整性,尤其是在多操作、多资源的环境中。本Demo将深入探讨Spring如何...

    Spring 源代码解析

    Spring源代码解析6:Spring声明式事务处理 ; Spring源代码解析7:Spring AOP中对拦截器调用的实现 Spring源代码解析8:Spring驱动Hibernate的实现;Spring源代码解析9:Spring Acegi框架鉴权的实现 Spring源...

    Spring源代码解析.rar

    Spring源代码解析6:Spring声明式事务处理 .doc Spring源代码解析7:Spring AOP中对拦截器调用的实现 .doc Spring源代码解析8:Spring驱动Hibernate的实现.doc Spring源代码解析9:Spring Acegi框架鉴权的实现.doc ...

    Spring事务管理开发必备jar包

    在Spring框架中,事务管理是实现业务逻辑时不可或缺的一部分,它确保了数据的一致性和完整性。本资源包提供了进行Spring事务管理开发所需的所有关键库,包括框架基础、核心组件、AOP(面向切面编程)支持、日志处理...

    spring事务管理

    #### 一、Spring事务管理概述 Spring框架提供了强大的事务管理功能,使得开发者能够更方便地管理应用程序中的事务。Spring事务管理主要包括两种类型:编程式事务管理和声明式事务管理。 - **编程式事务管理**:...

    Spring技术内幕:深入解析Spring架构与设计原理.pdf

    Spring技术内幕:深入解析Spring架构与设计原理 Spring技术内幕 Spring是一个基于Java的开源框架,旨在简化Java企业应用的开发。Spring的目标是提供一个简洁、灵活、可扩展的框架,以帮助开发者快速构建企业级...

    Spring技术内幕:深入解析Spring架构与设计原理(第2版) .pdf

    《Spring技术内幕:深入解析Spring架构与设计原理(第2版)》这本书主要聚焦于Spring框架的核心架构和技术细节,帮助读者全面理解Spring的工作机制、设计理念以及实现方式。下面将根据书名及其描述来展开相关知识点。 ...

    Spring源代码解析(六):Spring声明式事务处理.doc

    这个代理对象使用 TransactionInterceptor 来实现事务处理,TransactionInterceptor 是一个 AOP 的拦截器,它封装了事务管理的代码。 在整个源代码分析中,我们可以看到 Spring 实现声明式事务管理有三个部分: 1....

    Spring源代码解析

    Spring源代码解析(一):IOC容器 Spring源代码解析(二):IoC容器在Web容器中的启动 Spring源代码解析(三):Spring JDBC Spring源代码解析(四):Spring MVC Spring源代码解析(五):Spring AOP获取Proxy Spring源...

    spring事务源码解析

    spring事务源码解析

    spring事务管理5种方法

    在IT行业中,Spring框架是Java企业级应用开发的首选,其强大的事务管理功能是它的一大亮点。本篇文章将深入探讨Spring事务管理的五种方法,旨在帮助开发者更好地理解和运用这一核心特性。 首先,我们来了解什么是...

    spring 事务管理的理解

    Spring 提供了两种事务管理方式:编程式事务管理和声明式事务管理。 1. 编程式事务管理:这是通过编写代码来控制事务的开始、提交和回滚。Spring 提供了PlatformTransactionManager接口,如...

    spring事务与数据库操作

    #### 一、Spring的声明式事务管理 在现代软件开发中,事务处理是非常关键的一部分,特别是在涉及多个数据操作时。Spring框架提供了强大的事务管理能力,可以方便地集成到应用程序中。Spring支持两种类型的事务管理...

    Spring事务传播特性解析

    通过代码解析spring传播特性,包括 1、Propagation.REQUIRED 方法被调用时自动开启事务,在事务范围内使用则使用同一个事务,否则开启新事务。 2、Propagation.REQUIRES_NEW 无论何时自身都会开启事务 3、...

    实验 spring 声明事务

    实验 "Spring 声明事务" 是 Java 高级编程中的一个重要环节,旨在让学生掌握 Spring 框架中声明式事务管理的配置和使用。在实际应用中,事务管理是确保数据一致性、完整性和可靠性的关键组件。Spring 提供了声明式...

    Spring的事务管理小案例

    Spring是一个广泛应用的Java企业级应用开发框架,它提供了强大的事务管理功能,使得开发者可以方便地控制事务的边界,保证数据的一致性和完整性。 首先,理解事务(Transaction)是数据库操作的基础概念。事务具有...

    spring3.0两种事务管理配置

    这种方法只需要在 Spring 配置文件中定义一个事务管理对象(如 DataSourceTransactionManager),然后加入 `&lt;tx:annotation-driven/&gt;` 节点,引用该事务管理对象,然后即可在需要进行事务处理的类和方法使用 `@...

    Spring事务流程图

    Spring事务管理是Spring框架的核心特性之一,主要用于处理应用程序中的数据一致性问题。在Spring中,事务管理分为编程式和声明式两种方式。本篇文章将详细解释Spring事务管理的流程,以及如何通过时序图来理解这一...

    MyBatis 事务管理解析:颠覆你心中对事务的理解!.docx

    MyBatis 事务管理解析: 在理解 MyBatis 的事务管理之前,首先需要明确事务的基本概念。事务是数据库操作的基本单元,确保数据的一致性和完整性。通常,事务包含四个特性,即原子性(Atomicity)、一致性...

Global site tag (gtag.js) - Google Analytics