Spring事务管理--数据连接泄漏
看下面的代码,其中连接池采用的c3p0,配置文件省略
	import java.sql.Connection;
	import org.springframework.jdbc.core.JdbcTemplate;
	import org.springframework.stereotype.Service;
	import org.springframework.transaction.annotation.Propagation;
	import org.springframework.transaction.annotation.Transactional;
	
	
	
	@Service(value="jdbcSpring")
	public class JdbcSpring implements IJdbcSpring {
	    @Resource(name="jdbcTemplate")
	    private JdbcTemplate jt;
	
	   
	    /* (non-Javadoc)
	     * @see sping.jdbc.IJdbcSpring#addPerson()
	     */
	    @Override
	    @Transactional(propagation=Propagation.REQUIRED)//3
	    public void addPerson(){
	        try {
Connection con=jt.getDataSource().getConnection();// 1,获取连接
jt.execute("insert into person(pname) values ('哈哈')");
	    } catch (Exception e) {
	            // TODO Auto-generated catch block
	            e.printStackTrace();
	        }
	      
	    }
	
	}
此代码在1处显示的从数据源里获取一条连接,而在程序最后并没有关闭释放这个连接,则这个连接一直处于被暂用状态,造成了连接的泄露。我们写一段junit测试代码,其中的测试方法为
	package sping.jdbc.test;
	
	import org.junit.AfterClass;
	import org.junit.BeforeClass;
	import org.junit.Test;
	import org.springframework.context.ApplicationContext;
	import org.springframework.context.support.ClassPathXmlApplicationContext;
	
	import com.mchange.v2.c3p0.ComboPooledDataSource;
	
	import sping.jdbc.IJdbcSpring;
	
	public class SpringJdbcTest {
	    private static ApplicationContext context;
	
	    @BeforeClass
	    public static void setUpBeforeClass() throws Exception {
	        context = new ClassPathXmlApplicationContext(
	                new String[] { "jdbc.xml" });
	    }
	
	    @AfterClass
	    public static void tearDownAfterClass() throws Exception {
	    }
	
	    @Test
	    public void test() {
	        try {
	            ComboPooledDataSource cpds = context.getBean("dataSource",
	                    ComboPooledDataSource.class);
	            IJdbcSpring jtm = context.getBean("jdbcSpring", IJdbcSpring.class);
	            for (int i = 0; i < 10; i++) {
jtm.addPerson();// 2
	                System.out.println("---链接总数量" + cpds.getNumConnections()
	                        + "使用中的链接" + cpds.getNumBusyConnections() + "空闲的链接"
	                        + cpds.getNumIdleConnectionsAllUsers());
	            }
	        } catch (Exception e) {
	            e.printStackTrace();
	        }
	    }
	
	}
	我们在2处调用addPerson()方法,让我们来运行程序看一下结果
	---链接总数量3使用中的链接2空闲的链接2
	---链接总数量3使用中的链接3空闲的链接0
	---链接总数量6使用中的链接4空闲的链接2
	---链接总数量6使用中的链接5空闲的链接1
	---链接总数量6使用中的链接6空闲的链接0
	---链接总数量9使用中的链接7空闲的链接2
	---链接总数量9使用中的链接8空闲的链接1
	---链接总数量9使用中的链接9空闲的链接0
	---链接总数量12使用中的链接10空闲的链接2
	---链接总数量12使用中的链接11空闲的链接1
我们可以看到连接的总数在不断的增加,使用的连接也在不断的增加,这说明有连接泄露,
原因是直接通过数据源获取连接(jdbcTemplate.getDataSource().getConnection())而没有显式释放造成的。当然你可以手动去显示的释放连接。这就多多少少的改为手工管理了,有点和spring事务管理机制相违背的味道。
那么,应该怎么样从数据源中往外取连接呢?,通过DataSourceUtils 来获取连接,看下面的代码
我们先来看一下org.springframework.jdbc.datasource.DataSourceUtils中重要的方法
	/**
	     * Actually obtain a JDBC Connection from the given DataSource.
	     * Same as {@link #getConnection}, but throwing the original SQLException.
	     * <p>Is aware of a corresponding Connection bound to the current thread, for example
	     * when using {@link DataSourceTransactionManager}. Will bind a Connection to the thread
	     * if transaction synchronization is active (e.g. if in a JTA transaction).
	     * <p>Directly accessed by {@link TransactionAwareDataSourceProxy}.
	     * @param dataSource the DataSource to obtain Connections from
	     * @return a JDBC Connection from the given DataSource
	     * @throws SQLException if thrown by JDBC methods
	     * @see #doReleaseConnection
	     */
	    public static Connection doGetConnection(DataSource dataSource) throws SQLException
	      首先尝试从事务上下文中获取连接,失败后再从数据源获取连接
	    public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
	        try {
	            return doGetConnection(dataSource);
	        }
	        catch (SQLException ex) {
	            throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex);
	        }
	    }
可见此方法内部实现就是用doGetConnection(DataSource dataSource)方法来实现的,功能和上面的方法一样。
public static void doReleaseConnection(Connection con, DataSource dataSource) throws SQLException
此方法为释放连接,放回连接池中
	public static void releaseConnection(Connection con, DataSource dataSource) {
	        try {
	            doReleaseConnection(con, dataSource);
	        }
	        catch (SQLException ex) {
	            logger.debug("Could not close JDBC Connection", ex);
	        }
	        catch (Throwable ex) {
	            logger.debug("Unexpected exception on closing JDBC Connection", ex);
	        }
	    }
此方法就是通过上面的方法实现的,所以功能也一样,释放连接,归还到连接池
我们现在用DatasourceUtils来获取连接
我们只需要将代码1处获取连接的代码替换成
DataSourceUtils.getConnection(jt.getDataSource());
运行程序,控制台程序输出
	---链接总数量3使用中的链接1空闲的链接2
	---链接总数量3使用中的链接1空闲的链接2
	---链接总数量3使用中的链接1空闲的链接2
	---链接总数量3使用中的链接1空闲的链接2
	---链接总数量3使用中的链接1空闲的链接2
	---链接总数量3使用中的链接1空闲的链接2
	---链接总数量3使用中的链接1空闲的链接2
	---链接总数量3使用中的链接1空闲的链接2
	---链接总数量3使用中的链接1空闲的链接2
	---链接总数量3使用中的链接1空闲的链接2
	说明已经没有连接泄露了,我们通过DataSourceUtils的
	public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException
这个方法来获取连接,并没有显示的释放连接,连接并没有泄露。
那么,我们使用DataSourceUtils工具类来获取连接是否就非常的安全了呢?答案是否定的。
因为通过DataSourceUtils工具类在没有事务上下文的方法中获取连接使用,也会造成连接泄露。
我们从配置文件xml中删除事务的配置。本来不想贴xml配置了,还是贴上吧,
	<?xml version="1.0" encoding="UTF-8"?>
	
	<beans xmlns="http://www.springframework.org/schema/beans"
	    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
	    xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
	    xsi:schemaLocation="http://www.springframework.org/schema/beans  
	          http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
	           http://www.springframework.org/schema/context  
	           http://www.springframework.org/schema/context/spring-context-3.0.xsd  
	           http://www.springframework.org/schema/aop  
	           http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
	            http://www.springframework.org/schema/tx
	            http://www.springframework.org/schema/tx/spring-tx-3.0.xsd ">
	
	
	    <context:component-scan base-package="sping.jdbc*"></context:component-scan>
	
	    <!-- 数据源默认将autoCommit设置为true -->
	    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
	        destroy-method="close">
	        <property name="driverClass" value="${jdbc.driverClassName}" />
	        <property name="jdbcUrl" value="${jdbc.url}" />
	        <property name="user" value="${jdbc.username}" />
	        <property name="password" value="${jdbc.password}" />
	    </bean>
	
	    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
	        <property name="dataSource">
	            <ref bean="dataSource" />
	        </property>
	    </bean>
	<!--这里是事务配置,-->
	<!--
	    <bean id="myTxManager"
	        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	        <property name="dataSource" ref="dataSource" >
	        </property>
	    </bean>
	    <tx:annotation-driven transaction-manager="myTxManager" />
	-->
	    <context:property-placeholder location="jdbc.properties" />
	</beans>
再次运行程序,输出如下
	---链接总数量3使用中的链接2空闲的链接1
	---链接总数量3使用中的链接3空闲的链接0
	---链接总数量6使用中的链接4空闲的链接2
	---链接总数量6使用中的链接5空闲的链接1
	---链接总数量6使用中的链接6空闲的链接0
	---链接总数量9使用中的链接7空闲的链接2
	---链接总数量9使用中的链接8空闲的链接1
	---链接总数量9使用中的链接9空闲的链接0
	---链接总数量12使用中的链接10空闲的链接2
	---链接总数量12使用中的链接11空闲的链接1
数据连接有开始泄露来了!那么我们应该怎么做呢。要想避免连接泄露,我们须手动去关闭连接了,通过DataSourceUtils工具类
改造addPerson()方法,加上finally,如
	    public void addPerson() {
	        Connection con = null;
	        try {
	            // con=jt.getDataSource().getConnection();
	            con = DataSourceUtils.getConnection(jt.getDataSource());
	
	            jt.execute("insert into person(pname) values ('王江涛')");
	        } catch (Exception e) {
	
	        } finally {
	            DataSourceUtils.releaseConnection(con, jt.getDataSource());
	        }
	    }
	再次运行程序,结果如下
	---链接总数量3使用中的链接2空闲的链接2
	---链接总数量3使用中的链接2空闲的链接2
	---链接总数量3使用中的链接1空闲的链接2
	---链接总数量3使用中的链接2空闲的链接1
	---链接总数量3使用中的链接2空闲的链接1
	---链接总数量3使用中的链接2空闲的链接1
	---链接总数量3使用中的链接2空闲的链接2
	---链接总数量3使用中的链接2空闲的链接2
	---链接总数量3使用中的链接2空闲的链接2
	---链接总数量3使用中的链接2空闲的链接2
我们看到连接数量好像不对称了,最后一行,使用的2,空闲的2,而连接总数为3,具体什么原因我没有去深究,我的猜想是连接回收时有延迟, 而回收连接结束正好是再4处完成,所以空闲数量就增加了1,所以 造成了连接的不对称?
System.out.println("---链接总数量" + cpds.getNumConnections()+ "使用中的链接" + cpds.getNumBusyConnections() +"4"+"空闲的链接"+ cpds.getNumIdleConnectionsAllUsers());
不同数据访问框架 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 |