zoukankan      html  css  js  c++  java
  • 连接池

    一、连接池原理
          在实际应用开发中,特别是在WEB应用系统中,如果JSP、Servlet或EJB使用JDBC直接访问数据库中的数据,每一次数据访问请求都必须经历建立数据库连接、打开数据库、存取数据和关闭数据库连接等步骤,而连接并打开数据库是一件既消耗资源又费时的工作,如果频繁发生这种数据库操作,系统的性能必然会急剧下降,甚至会导致系统崩溃。数据库连接池技术是解决这个问题最常用的方法,在许多应用程序服务器(例如:Weblogic,WebSphere,JBoss)中,基本都提供了这项技术,无需自己编程,但是,深入了解这项技术是非常必要的。
          数据库连接池技术的思想非常简单,将数据库连接作为对象存储在一个Vector对象中,一旦数据库连接建立后,不同的数据库访问请求就可以共享这些连接,这样,通过复用这些已经建立的数据库连接,可以克服上述缺点,极大地节省系统资源和时间。
          数据库连接池的主要操作如下:
    (1)建立数据库连接池对象(服务器启动)。
    (2)按照事先指定的参数创建初始数量的数据库连接(即:空闲连接数)。
    (3)对于一个数据库访问请求,直接从连接池中得到一个连接。如果数据库连接池对象中没有空闲的连接,且连接数没有达到最大(即:最大活跃连接数),创建一个新的数据库连接。
    (4)存取数据库。
    (5)关闭数据库,释放所有数据库连接(此时的关闭数据库连接,并非真正关闭,而是将其放入空闲队列中。如实际空闲连接数大于初始空闲连接数则释放连接)。
    (6)释放数据库连接池对象(服务器停止、维护期间,释放数据库连接池对象,并释放所有连接)。
    二.常见的数据库连接池
    常见的数据库连接池有:proxool,c3p0,dbcp,DBPool,还有各个应用服务器自带的连接池.
    hibernate开发组推荐使用c3p0;
    spring开发组推荐使用dbcp(dbcp连接池有weblogic连接池同样的问题,就是强行关闭连接或数据库重启后,无法reconnect ,告诉连接被重置,这个设置可以解决);
    hibernate in action推荐使用c3p0和proxool;
    三、连接池中常见的参数概述
    (1)dbcp:
    driverClassName 驱动
    url 连接字符串
    username 用户名
    password 密码
    maxActive 连接池支持的最大连接数
    maxIdle 连接池中最多可空闲maxIdle个连接
    minIdle 连接池中最少可空闲maxIdle个连接
    initialSize 初始化连接数目
    maxWait 连接池中连接用完时,新的请求等待时间,毫秒

    timeBetweenEvictionRunsMillis、timeBetweenEvictionRunsMillis和minEvictableIdleTimeMillis一起使用,
    每timeBetweenEvictionRunsMillis毫秒检查一次连接池中空闲的连接,把空闲时间超过minEvictableIdleTimeMillis毫秒的连接断开,直到连接池中的连接数到minIdle为止。

    minEvictableIdleTimeMillis 连接池中连接可空闲的时间,毫秒
    removeAbandoned true,false,是否清理removeAbandonedTimeout秒没有使用的活动连接,清理后并没有放回连接池
    removeAbandonedTimeout 活动连接的最大空闲时间
    logAbandoned true,false,连接池收回空闲的活动连接时是否打印消息

    minEvictableIdleTimeMillis,removeAbandonedTimeout这两个参数针对的连接对象不一样,minEvictableIdleTimeMillis针对连接池中的连接对象,removeAbandonedTimeout针对未被close的活动连接.

    在dbcp使用中遇到的问题:
    当短时间之内活动连接达到maxActive,再请求连接,等maxWait秒后连接池就会报出错来:Cannot get a connection, pool exhausted.在这maxWait秒里removeAbandoned并没有起作用,出错后连接池就会把所有的连接断开,为什么这时候 removeAbandoned没有起作用呢?

    (2)c3p0:
    driverClass
    jdbcUrl
    user
    password
    minPoolSize
    maxPoolSize
    initialPoolSize

    acquireIncrement 池中没有空闲连接时,一次请求获取的连接数
    maxIdleTime 池中连接最大空闲时间
    acquireRetryAttempts 获取连接失败后,重新尝试的次数
    acquireRetryDelay 尝试连接间隔时间,毫秒
    checkoutTimeout 等待连接时间,0为无限等待,毫秒
    DebugUnreturnedConnectionStackTraces true,false,是否收回未返回的活动连接
    unreturnedConnectionTimeout 活动连接的时间.

    c3p0中的问题:
    unreturnedConnectionTimeout是给每个活动连接一个时间限制,到点儿就收回,不管有没有正在使用连接.这样不是太好,应该是从最后一次使用连接才开始计时才好.那有没有这样的一个参数从最后一次使用计时呢?

    四、以下通过案例模拟数据库连接池的内部原理,让开发人员进一步了解数据库连接池原理

    1、引入需要的jar,如下图所示:

    2、项目结构,如下图所示:

    3、ConnectionPool类,数据库连接池

    package com.ljq.sql;
    
    import java.sql.Connection;
    import java.sql.DatabaseMetaData;
    import java.sql.Driver;
    import java.sql.DriverManager;
    import java.sql.SQLException;
    import java.sql.Statement;
    import java.util.Enumeration;
    import java.util.Vector;
    
    @SuppressWarnings("unchecked")
    public class ConnectionPool {
        private String jdbcDriver = "oracle.jdbc.driver.OracleDriver"; // 数据库驱动
        private String dbUrl = "jdbc:oracle:thin:@localhost:1521:orcl"; // 数据 URL
        private String dbUsername = "test"; // 数据库用户名
        private String dbPassword = "test"; // 数据库用户密码
        private String testTable = ""; // 测试连接是否可用的测试表名,默认没有测试表
        private int initialConnections = 10; // 连接池的初始大小
        private int incrementalConnections = 5;// 连接池自动增加的大小
        private int maxConnections = 50; // 连接池最大的大小
        private Vector connections = null; // 存放连接池中数据库连接的向量, 初始时为 null,它中存放的对象为PooledConnection 型
    
        public ConnectionPool(){
            
        }
        
        /**
         * 构造函数
         * 
         * @param jdbcDriver
         *            String JDBC 驱动类串
         * @param dbUrl
         *            String 数据库 URL
         * @param dbUsername
         *            String 连接数据库用户名
         * @param dbPassword
         *            String 连接数据库用户的密码
         */
    
        public ConnectionPool(String jdbcDriver, String dbUrl, String dbUsername,
                String dbPassword) {
            this.jdbcDriver = jdbcDriver;
            this.dbUrl = dbUrl;
            this.dbUsername = dbUsername;
            this.dbPassword = dbPassword;
        }
    
        /**
         * 返回连接池的初始大小
         * 
         * @return 初始连接池中可获得的连接数量
         */
        public int getInitialConnections() {
            return this.initialConnections;
        }
    
        /**
         * 设置连接池的初始大小
         * 
         * @param 用于设置初始连接池中连接的数量
         */
        public void setInitialConnections(int initialConnections) {
            this.initialConnections = initialConnections;
        }
    
        /**
         * 返回连接池自动增加的大小
         * 
         * @return 连接池自动增加的大小
         */
        public int getIncrementalConnections() {
            return this.incrementalConnections;
        }
    
        /**
         * 设置连接池自动增加的大小
         * 
         * @param 连接池自动增加的大小
         */
    
        public void setIncrementalConnections(int incrementalConnections) {
            this.incrementalConnections = incrementalConnections;
        }
    
        /**
         * 返回连接池中最大的可用连接数量
         * 
         * @return 连接池中最大的可用连接数量
         */
        public int getMaxConnections() {
            return this.maxConnections;
        }
    
        /**
         * 设置连接池中最大可用的连接数量
         * 
         * @param 设置连接池中最大可用的连接数量值
         */
        public void setMaxConnections(int maxConnections) {
            this.maxConnections = maxConnections;
        }
    
        /**
         * 获取测试数据库表的名字
         * 
         * @return 测试数据库表的名字
         */
        public String getTestTable() {
            return this.testTable;
        }
    
        /**
         * 设置测试表的名字
         * 
         * @param testTable
         *            String 测试表的名字
         */
        public void setTestTable(String testTable) {
            this.testTable = testTable;
        }
    
        /**
         * 创建一个数据库连接池,连接池中的可用连接的数量采用类成员
         * 
         * initialConnections 中设置的值
         */
        public synchronized void createPool() throws Exception {
            // 确保连接池没有创建
            // 如果连接池己经创建了,保存连接的向量connections不会为空
            if (connections != null) {
                return; // 如果己经创建,则返回
            }
    
            // 实例化JDBC Driver中指定的驱动类实例
            Driver driver = (Driver) (Class.forName(this.jdbcDriver).newInstance());
            DriverManager.registerDriver(driver); // 注册JDBC驱动程序
            // 创建保存连接的向量 , 初始时有0个元素
            connections = new Vector();
            // 根据initialConnections中设置的值,创建连接。
            createConnections(this.initialConnections);
            System.out.println(" 数据库连接池创建成功! ");
        }
    
        /**
         * 创建由 numConnections 指定数目的数据库连接 , 并把这些连接放入 connections 向量中
         * 
         * @param numConnections
         *            要创建的数据库连接的数目
         */
        @SuppressWarnings("unchecked")
        private void createConnections(int numConnections) throws SQLException {
            // 循环创建指定数目的数据库连接
            for (int x = 0; x < numConnections; x++) {
                // 是否连接池中的数据库连接的数量己经达到最大?
                // 最大值由类成员 maxConnections指出,如果 maxConnections 为 0 或负数,表示连接数量没有限制。
                // 如果连接数己经达到最大,即退出。
                if (this.maxConnections > 0
                        && this.connections.size() >= this.maxConnections) {
                    break;
                }
    
                // add a new PooledConnection object to connections vector
                // 增加一个连接到连接池中(向量 connections 中)
                try {
                    connections.addElement(new PooledConnection(newConnection()));
                } catch (SQLException e) {
                    System.out.println(" 创建数据库连接失败! " + e.getMessage());
                    throw new SQLException();
                }
    
                System.out.println(" 数据库连接己创建 ......");
            }
    
        }
    
        /**
         * 创建一个新的数据库连接并返回它
         * 
         * @return 返回一个新创建的数据库连接
         */
        private Connection newConnection() throws SQLException {
            // 创建一个数据库连接
            Connection conn = DriverManager.getConnection(dbUrl, dbUsername, dbPassword);
            // 如果这是第一次创建数据库连接,即检查数据库,获得此数据库允许支持的最大客户连接数目
            // connections.size()==0 表示目前没有连接己被创建
            if (connections.size() == 0) {
                DatabaseMetaData metaData = conn.getMetaData();
                int driverMaxConnections = metaData.getMaxConnections();
                // 数据库返回的 driverMaxConnections 若为 0 ,表示此数据库没有最大连接限制,或数据库的最大连接限制不知道
                // driverMaxConnections 为返回的一个整数,表示此数据库允许客户连接的数目
                // 如果连接池中设置的最大连接数量大于数据库允许的连接数目 , 则置连接池的最大连接数目为数据库允许的最大数目
                if (driverMaxConnections > 0
                        && this.maxConnections > driverMaxConnections) {
                    this.maxConnections = driverMaxConnections;
                }
            }
            return conn; // 返回创建的新的数据库连接
        }
    
        /**
         * 通过调用 getFreeConnection() 函数返回一个可用的数据库连接 , 如果当前没有可用的数据库连接,
         * 并且更多的数据库连接不能创建(如连接池大小的限制),此函数等待一会再尝试获取。
         * 
         * @return 返回一个可用的数据库连接对象
         */
        public synchronized Connection getConnection() throws SQLException {
            // 确保连接池己被创建
            if (connections == null) {
                return null; // 连接池还没创建,则返回 null
            }
    
            Connection conn = getFreeConnection(); // 获得一个可用的数据库连接
            // 如果目前没有可以使用的连接,即所有的连接都在使用中
            while (conn == null) {
                // 等一会再试
                wait(250);
                // 重新再试,直到获得可用的连接,如果getFreeConnection() 返回的为 null 
                // 则表明创建一批连接后也不可获得可用连接
                conn = getFreeConnection(); 
            }
            
            return conn; // 返回获得的可用的连接
        }
    
        /**
         * 本函数从连接池向量 connections 中返回一个可用的的数据库连接,如果 当前没有可用的数据库连接,
         * 本函数则根据incrementalConnections 设置 的值创建几个数据库连接,并放入连接池中。 
         * 如果创建后,所有的连接仍都在使用中,则返回 null
         * 
         * @return 返回一个可用的数据库连接
         */
        private Connection getFreeConnection() throws SQLException {
            // 从连接池中获得一个可用的数据库连接
            Connection conn = findFreeConnection();
    
            if (conn == null) {
                // 如果目前连接池中没有可用的连接
                // 创建一些连接
                createConnections(incrementalConnections);
                // 重新从池中查找是否有可用连接
                conn = findFreeConnection();
                if (conn == null) {
                    // 如果创建连接后仍获得不到可用的连接,则返回 null
                    return null;
                }
            }
    
            return conn;
        }
    
        /**
         * 查找连接池中所有的连接,查找一个可用的数据库连接,如果没有可用的连接,返回 null
         * 
         * @return 返回一个可用的数据库连接
         */
        private Connection findFreeConnection() throws SQLException {
            Connection conn = null;
            PooledConnection pConn = null;
            // 获得连接池向量中所有的对象
            Enumeration enumerate = connections.elements();
    
            // 遍历所有的对象,看是否有可用的连接
            while (enumerate.hasMoreElements()) {
                pConn = (PooledConnection) enumerate.nextElement();
                if (!pConn.isBusy()) {
                    // 如果此对象不忙,则获得它的数据库连接并把它设为忙
                    conn = pConn.getConnection();
                    pConn.setBusy(true);
                    // 测试此连接是否可用
                    if (!testConnection(conn)) {
                        // 如果此连接不可再用了,则创建一个新的连接,并替换此不可用的连接对象,如果创建失败,返回 null
                        try {
                            conn = newConnection();
                        } catch (SQLException e) {
                            System.out.println(" 创建数据库连接失败! " + e.getMessage());
                            return null;
                        }
                        pConn.setConnection(conn);
                    }
                    break; // 己经找到一个可用的连接,退出
                }
            }
            return conn;// 返回找到到的可用连接
        }
    
        /**
         * 测试一个连接是否可用,如果不可用,关掉它并返回false,否则可用返回true
         * 
         * @param conn
         *            需要测试的数据库连接
         * 
         * @return 返回 true 表示此连接可用, false 表示不可用
         */
        private boolean testConnection(Connection conn) {
            try {
                // 判断测试表是否存在
                if (testTable.equals("")) {
                    // 如果测试表为空,试着使用此连接的 setAutoCommit()方法来判断连接是否可用(此方法只在部分数据库可用,如果不可用,抛出异常)。注意:使用测试表的方法更可靠
                    conn.setAutoCommit(true);
                } else {
                    // 有测试表的时候使用测试表测试
                    // check if this connection is valid
                    Statement stmt = conn.createStatement();
                    stmt.execute("select count(*) from " + testTable);
                }
            } catch (SQLException e) {
                // 上面抛出异常,此连接己不可用,关闭它,并返回 false;
                closeConnection(conn);
                return false;
            }
    
            // 连接可用,返回 true
            return true;
        }
    
        /**
         * 返回一个数据库连接到连接池中,并把此连接置为空闲。 
         * 所有使用连接池获得的数据库连接均应在不使用此连接时返回它。
         * 
         * @param 需返回到连接池中的连接对象
         */
        public void returnConnection(Connection conn) {
            // 确保连接池存在,如果连接没有创建(不存在),直接返回
            if (connections == null) {
                System.out.println(" 连接池不存在,无法返回此连接到连接池中 !");
                return;
            }
    
            PooledConnection pConn = null;
            Enumeration enumerate = connections.elements();
            // 遍历连接池中的所有连接,找到这个要返回的连接对象
            while (enumerate.hasMoreElements()) {
                pConn = (PooledConnection) enumerate.nextElement();
                // 先找到连接池中的要返回的连接对象
                if (conn == pConn.getConnection()) {
                    // 找到了 , 设置此连接为空闲状态
                    pConn.setBusy(false);
                    break;
                }
            }
        }
    
        /**
         * 刷新连接池中所有的连接对象
         * 
         */
        public synchronized void refreshConnections() throws SQLException {
            // 确保连接池己创新存在
            if (connections == null) {
                System.out.println(" 连接池不存在,无法刷新 !");
                return;
            }
    
            PooledConnection pConn = null;
            Enumeration enumerate = connections.elements();
            while (enumerate.hasMoreElements()) {
                // 获得一个连接对象
                pConn = (PooledConnection) enumerate.nextElement();
                // 如果对象忙则等 5 秒 ,5 秒后直接刷新
                if (pConn.isBusy()) {
                    wait(5000); // 等 5 秒
                }
    
                // 关闭此连接,用一个新的连接代替它。
                closeConnection(pConn.getConnection());
                pConn.setConnection(newConnection());
                pConn.setBusy(false);
            }
        }
    
        /**
         * 关闭连接池中所有的连接,并清空连接池。
         * 
         */
        public synchronized void closeConnectionPool() throws SQLException {
            // 确保连接池存在,如果不存在,返回
            if (connections == null) {
                System.out.println(" 连接池不存在,无法关闭 !");
                return;
            }
    
            PooledConnection pConn = null;
            Enumeration enumerate = connections.elements();
            while (enumerate.hasMoreElements()) {
                pConn = (PooledConnection) enumerate.nextElement();
                // 如果忙,等 5 秒
                if (pConn.isBusy()) {
                    wait(5000); // 等 5 秒
                }
                // 5 秒后直接关闭它
                closeConnection(pConn.getConnection());
                // 从连接池向量中删除它
                connections.removeElement(pConn);
            }
    
            // 置连接池为空
            connections = null;
        }
    
        /**
         * 关闭一个数据库连接
         * 
         * @param 需要关闭的数据库连接
         * 
         */
        private void closeConnection(Connection conn) {
            try {
                if (conn != null) {
                    conn.close();
                    conn = null;
                }
            } catch (SQLException e) {
                System.out.println(" 关闭数据库连接出错: " + e.getMessage());
                e.printStackTrace();
            }
        }
    
        /**
         * 使程序等待给定的毫秒数
         * 
         * @param 给定的毫秒数
         * 
         */
    
        private void wait(int mSeconds) {
            try {
                Thread.sleep(mSeconds);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 内部使用的用于保存连接池中连接对象的类
         * 
         * 此类中有两个成员,一个是数据库的连接,另一个是指示此连接是否正在使用的标志。
         */
    
        class PooledConnection {
            Connection connection = null;// 数据库连接
            boolean busy = false; // 此连接是否正在使用的标志,默认没有正在使用
    
            // 构造函数,根据一个 Connection 构告一个 PooledConnection 对象
            public PooledConnection(Connection connection) {
                this.connection = connection;
            }
    
            // 返回此对象中的连接
            public Connection getConnection() {
                return connection;
            }
    
            // 设置此对象的,连接
            public void setConnection(Connection connection) {
                this.connection = connection;
            }
    
            // 获得对象连接是否忙
            public boolean isBusy() {
                return busy;
            }
    
            // 设置对象的连接正在忙
            public void setBusy(boolean busy) {
                this.busy = busy;
            }
    
        }
    
    }

    四、ConnectionPoolTest测试类

    package junit.test;
    
    import java.sql.Connection;
    
    import org.junit.Test;
    
    import com.ljq.sql.ConnectionPool;
    
    public class ConnectionPoolTest {
    
        /**
         * 连接池调用方法一
         * @throws Exception
         */
        @Test
        public void conn() throws Exception{
            ConnectionPool connPool=new ConnectionPool();
            connPool.createPool();
            Connection conn=connPool.getConnection();
            System.out.println("conn: "+conn);
        }
        
        /**
         * 连接池调用方法二
         * 
         * @throws Exception
         */
        @Test
        public void conn2() throws Exception{
            ConnectionPool connPool=new ConnectionPool("oracle.jdbc.driver.OracleDriver", 
                    "jdbc:oracle:thin:@localhost:1521:orcl", 
                    "test", 
                    "test");
            connPool.createPool();
            Connection conn=connPool.getConnection();
            System.out.println("conn: "+conn);
        }
    }

    --------------------------------------------------------------------

    Blog:http://www.cnblogs.com/linjiqin/
    J2EE、Android、Linux、Oracle QQ交流群:142463980、158560018(满)

    题外话:
    本人来自铁观音的发源地——泉州安溪,正宗安溪铁观音,有需要的友友欢迎加我Q:416501600。
    茶叶淘宝店:http://shop61968332.taobao.com/  

  • 相关阅读:
    EBS SQL > Form & Report
    oracle sql 优化分析点
    MRP 物料需求计划
    MRPII 制造资源计划
    Barcode128 应用实务
    Oracle SQL语句优化技术分析
    APPSQLAP10710 Online accounting could not be created. AP Invoice 无法创建会计分录
    Oracle数据完整性和锁机制
    ORACLE Responsibility Menu Reference to Other User
    EBS 常用 SQL
  • 原文地址:https://www.cnblogs.com/linjiqin/p/2544683.html
Copyright © 2011-2022 走看看