首页 > 代码库 > Apache DBCP数据库连接池溢出调整

Apache DBCP数据库连接池溢出调整

数据库最大连接池溢出是在系统运行中比较常见的一个问题,在开发中,可以通过设置最大连接池的各位为1或者2,就能在开发的时候发现数据库连接没有被释放的情况。不过这个小技巧在hibernate和sping等框架大量使用之后就没什么用了。

数据库连接池溢出的源代码:

package test.ffm83.commons.dbcp;

 

import org.apache.commons.dbcp.BasicDataSource;

import org.apache.commons.dbcp.BasicDataSourceFactory;

import org.apache.commons.lang.StringUtils;

import java.sql.PreparedStatement;

import java.sql.ResultSet;

import java.sql.SQLException;

import java.sql.Connection;

import java.util.Properties;

/* 通过dbcp连接oracle数据库,模拟连接池溢出

 * 使用1.4版本实现

 * @author 范芳铭

 * */

public classDbcpUsageParamA {

    private static BasicDataSource dataSource =null;

    public DbcpUsageParamA() {

    }

    public static void init() {

        if (dataSource !=null) {

            try {

                dataSource.close();

            }catch(Exception e) {

                e.printStackTrace();

            }

            dataSource =null;

        }

        try {

            Propertiesp = newProperties();

            p.setProperty("driverClassName","oracle.jdbc.driver.OracleDriver");

            p.setProperty("url","jdbc:oracle:thin:@192.168.19.24:1521:fanfangming");

            p.setProperty("password","ffm");

            p.setProperty("username","ffm");

            p.setProperty("maxActive","2");//最大连接数

            p.setProperty("maxIdle","1");

            p.setProperty("maxWait","10");

 

            dataSource =(BasicDataSource) BasicDataSourceFactory

                    .createDataSource(p);

        }catch(Exception e) {

            e.printStackTrace();

        }

    }

 

    public static synchronized ConnectiongetConnection()throwsSQLException {

        if (dataSource ==null) {

            init();

        }

        Connectionconn = null;

        if (dataSource !=null) {

            conn= dataSource.getConnection();

        }

        return conn;

    }

 

    public static void main(String[] args)throws Exception {

        getConAndNotClose();

        getConAndNotClose();

        getConAndNotClose();

    }

   

    //本方法使用数据库连接但是不释放,用来模拟连接池溢出情况

    private static void getConAndNotClose()throws Exception{

        Connectioncon = null;

        try {

            con= DbcpUsageParamA.getConnection();

            Stringsql = " select sysdate from dual ";

            PreparedStatementps = con.prepareStatement(sql);

            ResultSetrs = ps.executeQuery();

            while (rs.next()) {

                Stringvalue = http://www.mamicode.com/rs.getString("sysdate");

                System.out.println(StringUtils.center(value+",数据库连接成功", 50,"-"));

            }

        }catch(Exception e) {

            e.printStackTrace();

        }

        //注意,这里没有关闭数据库连接池,实际代码不要这么写。

    }

}

运行结果:

----------2014-12-16 12:47:49.0,数据库连接成功-----------

----------2014-12-16 12:47:49.0,数据库连接成功-----------

org.apache.commons.dbcp.SQLNestedException: Cannot get a connection, pool error Timeout waiting for idle object

    atorg.apache.commons.dbcp.PoolingDataSource.getConnection(PoolingDataSource.java:114)

….

 

其他几个参数也可以这样去测试。

 

在配置时,主要难以理解的主要有:removeAbandoned 、logAbandoned、removeAbandonedTimeout、maxWait这四个参数,设置了rmoveAbandoned=true那么在getNumActive()快要到getMaxActive()的时候,系统会进行无效的Connection的回收,回收的Connection为removeAbandonedTimeout(默认300秒)中设置的秒数后没有使用的Connection。

如果开启"removeAbandoned",那么连接在被认为泄露时可能被池回收. 这个机制在(getNumIdle() < 2) and (getNumActive()> getMaxActive() - 3)时被触发.
举例当maxActive=20,活动连接为18,空闲连接为1时可以触发"removeAbandoned".

看了这么多,先看一个例子,关于removeAbandoned。

package test.ffm83.commons.dbcp;

 

import org.apache.commons.dbcp.BasicDataSource;

importorg.apache.commons.dbcp.BasicDataSourceFactory;

import org.apache.commons.lang.StringUtils;

import java.sql.PreparedStatement;

import java.sql.ResultSet;

import java.sql.SQLException;

import java.sql.Connection;

import java.util.Properties;

/* 通过dbcp连接oracle数据库,体验连不释放下的removeAbandoned等参数

 * 使用1.4版本实现

 * @author 范芳铭

 * */

public class DbcpAbandone {

privatestatic BasicDataSource dataSource = null;

publicDbcpAbandone() {

}

publicstatic void init() {

           if(dataSource != null) {

                    try{

                             dataSource.close();

                    }catch (Exception e) {

                             e.printStackTrace();

                    }

                    dataSource= null;

           }

           try{

                    Propertiesp = new Properties();

                    p.setProperty("driverClassName","oracle.jdbc.driver.OracleDriver");

                    p.setProperty("url","jdbc:oracle:thin:@192.168.1.1:1521:fanfangming");

                    p.setProperty("password","ffm");

                    p.setProperty("username","ffm");

                    p.setProperty("maxActive","6"); //

                    p.setProperty("maxIdle","3");

                    p.setProperty("maxWait","10");

                    p.setProperty("removeAbandoned","true");//移除不用的连接

                    p.setProperty("removeAbandonedTimeout","8");

                    p.setProperty("logAbandoned","true");

 

                    dataSource= (BasicDataSource) BasicDataSourceFactory

                                       .createDataSource(p);

           }catch (Exception e) {

                    e.printStackTrace();

           }

}

publicstatic synchronized Connection getConnection() throws SQLException {

           if(dataSource == null) {

                    init();

           }

           Connectionconn = null;

           if(dataSource != null) {

                    conn= dataSource.getConnection();

           }

           returnconn;

}

 

publicstatic void main(String[] args) throws Exception {

           for(inti=0;i < 10 ; i++){

                    getConAndNotClose(i+1);

           }

}

//本方法使用数据库连接但是不释放,用来模拟连接池溢出情况

privatestatic void getConAndNotClose(int id) throws Exception{

           Connectioncon = null;

           try{

                    con= DbcpAbandone.getConnection();

                    Stringsql = " select sysdate from dual ";

                    PreparedStatementps = con.prepareStatement(sql);

                    ResultSetrs = ps.executeQuery();

                    while(rs.next()) {

                             Stringvalue = http://www.mamicode.com/rs.getString("sysdate");

                            

                             Thread.sleep(6* 1000);

                             System.out.println(StringUtils.center(value+","+id+"数据库连接成功", 50, "-"));

                    }

           }catch (Exception e) {

                    e.printStackTrace();

           }

           //注意,这里没有关闭数据库连接池,实际代码不要这么写。

}

}

运行结果如下:

----------2014-12-16 14:12:45.0,1数据库连接成功----------

----------2014-12-16 14:12:53.0,2数据库连接成功----------

----------2014-12-16 14:13:00.0,3数据库连接成功----------

----------2014-12-16 14:13:06.0,4数据库连接成功----------

----------2014-12-16 14:13:13.0,5数据库连接成功----------

----------2014-12-16 14:13:20.0,6数据库连接成功----------

----------2014-12-16 14:13:27.0,7数据库连接成功----------

----------2014-12-16 14:13:33.0,8数据库连接成功----------

----------2014-12-16 14:13:41.0,9数据库连接成功----------

---------2014-12-1614:13:47.0,10数据库连接成功----------

10个数据库连接都生效了,简直太神奇了,removeAbandoned确实回收了部分连接,所以10个数据库都能够连接,那么有了removeAbandoned 参数是不是就以后能解决连接池溢出的问题呢,答案是只能缓解连接池问题,不能彻底解决,而且要和removeAbandonedTimeout参数配套使用。

removeAbandonedTimeout 参数要和maxWait(业务逻辑)参数配套使用。

在上面的代码中,有一段代码为:Thread.sleep(6 * 1000);

如果把这段代码注释掉,马上就能看到6个数据库连接之后,异常马上就来了。

我们把这段的异常取下来:

异常太长了就不直接贴了。

 

我们在代码中加了p.setProperty("logAbandoned","true"); 如果把

/*                          p.setProperty("removeAbandoned","true");//移除不用的连接

                    p.setProperty("removeAbandonedTimeout","8");

                    p.setProperty("logAbandoned","true");*/

之后的异常也取下来,保持之后看差异在哪里。

人笨只好用力做,把两端异常拿下来仔细分析,有那么一点点差异。

 

Caused by: java.util.NoSuchElementException:Timeout waiting for idle object

atorg.apache.commons.pool.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:1174)

atorg.apache.commons.dbcp.AbandonedObjectPool.borrowObject(AbandonedObjectPool.java:79)

atorg.apache.commons.dbcp.PoolingDataSource.getConnection(PoolingDataSource.java:106)

... 4 more

 

org.apache.commons.dbcp.AbandonedObjectPool.borrowObject(AbandonedObjectPool.java:79)有一点差异。

但是根据网上搜索到的资料,“logAbandoned=true的话,将会在回收事件后,在log中打印出回收Connection的错误信息,包括在哪个地方用了Connection却忘记关闭了,在调试的时候很有用。”

尝试了一些方法和时间,没有找到这个要怎么测试,只能先放下,等以后遇到了这个问题再来记录。

         经过一些测试后发现,这个日志不是一出现数据库连接池没有关闭就会被打印出来,需要一定的触发调整。在“Apache DBCP连接数据库异常重连”章节中的用例,出现了logAbandoned 打印出来的日志,养在深闺人未识啊,特摘录下来:这个日志,比较明确指出了那些地方数据库连接池没有关闭。

 

    attest.ffm83.commons.dbcp.DbcpErrorConnection.main(DbcpErrorConnection.java:71)

org.apache.commons.dbcp.AbandonedTrace$AbandonedObjectException: DBCP object created 2014-12-16 15:41:53 by the following code was neverclosed:

    atorg.apache.commons.dbcp.AbandonedTrace.init(AbandonedTrace.java:90)

    atorg.apache.commons.dbcp.AbandonedTrace.<init>(AbandonedTrace.java:73)

    atorg.apache.commons.dbcp.DelegatingStatement.<init>(DelegatingStatement.java:61)

    atorg.apache.commons.dbcp.DelegatingPreparedStatement.<init>(DelegatingPreparedStatement.java:70)

    atorg.apache.commons.dbcp.DelegatingConnection.prepareStatement(DelegatingConnection.java:281)


Apache DBCP数据库连接池溢出调整