热门标签 | HotTags
当前位置:  开发笔记 > 数据库 > 正文

数据库连接池DBCP浅析

一个典型的关系数据库应用:a.建立连接b.执行数据库操作c.关闭连接其中建立连接可能需要数ms才能完成。对于多并发请求的场景,频繁的建立连接、断开连接可能会成为程序与关系数据库交互的瓶颈。为此,很多时候,我们使用数据库连接池来复用连接,避免

一个典型的关系数据库应用: a. 建立连接 b. 执行数据库操作 c. 关闭连接 其中建立连接可能需要数ms才能完成。对于多并发请求的场景,频繁的建立连接、断开连接可能会成为程序与关系数据库交互的瓶颈。 为此,很多时候,我们使用数据库连接池来复用连接,避免

一个典型的关系数据库应用:
a. 建立连接
b. 执行数据库操作
c. 关闭连接

其中建立连接可能需要数ms才能完成。对于多并发请求的场景,频繁的建立连接、断开连接可能会成为程序与关系数据库交互的瓶颈。
为此,很多时候,我们使用数据库连接池来复用连接,避免频繁的建立连接带来的资源浪费。
DBCP是应用非常广泛的关系数据库连接池,和DBCP类似的有C3p0, Proxool

DBCP依赖commons-pool提供的连接池,其包括两个不同的版本:
a. DBCP 1.4只能运行在JDK1.6(JDBC 4)
b. DBCP 1.3只能运行在JDK 1.4-1.5(JDBC 3)


DBCP相关配置:DBCP configuration
说明:
dataSource: 要连接的 datasource (通常我们不会定义在 server.xml)
defaultAutoCommit: 对于事务是否 autoCommit, 默认值为 true
defaultReadOnly: 对于数据库是否只能读取, 默认值为 false
driverClassName:连接数据库所用的 JDBC Driver Class,
maxActive: 可以从对象池中取出的对象最大个数,为0则表示没有限制,默认为8
maxIdle: 最大等待连接中的数量,设 0 为没有限制 (对象池中对象最大个数)
minIdle:对象池中对象最小个数
maxWait: 最大等待秒数, 单位为 ms, 超过时间会丟出错误信息
password: 登陆数据库所用的密码
url: 连接数据库的 URL
username: 登陆数据库所用的帐号
validationQuery: 验证连接是否成功, SQL SELECT 指令至少要返回一行
removeAbandoned: 是否自我中断, 默认是 false
removeAbandonedTimeout: 几秒后会自我中断, removeAbandoned 必须为 true
logAbandoned: 是否记录中断事件, 默认为 false
minEvictableIdleTimeMillis:大于0 ,进行连接空闲时间判断,或为0,对空闲的连接不进行验证;默认30分钟
timeBetweenEvictionRunsMillis:失效检查线程运行时间间隔,如果小于等于0,不会启动检查线程,默认-1
testOnBorrow:取得对象时是否进行验证,检查对象是否有效,默认为false
testOnReturn:返回对象时是否进行验证,检查对象是否有效,默认为false
testWhileIdle:空闲时是否进行验证,检查对象是否有效,默认为false
initialSize:初始化线程数

DBCP原理
用户使用数据库连接池流程:
a. 从连接池中获取一个连接(如果有已建立空闲的连接,直接获取连接,否则建立新连接)
b. 执行数据库操作
c. 将连接归还给数据库连接池

源码分析(common-dbcp 1.4)

BasicDataSource是用户使用的类,DBCP项目组放出example:
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.Statement;
import java.sql.ResultSet;
import java.sql.SQLException;


import org.apache.commons.dbcp2.BasicDataSource;


public class BasicDataSourceExample {

    public static void main(String[] args) {
        // First we set up the BasicDataSource.
        // Normally this would be handled auto-magically by
        // an external configuration, but in this example we'll
        // do it manually.
        //
        System.out.println("Setting up data source.");
        DataSource dataSource = setupDataSource(args[0]);
        System.out.println("Done.");

        //
        // Now, we can use JDBC DataSource as we normally would.
        //
        Connection cOnn= null;
        Statement stmt = null;
        ResultSet rset = null;

        try {
            System.out.println("Creating connection.");
            cOnn= dataSource.getConnection();
            System.out.println("Creating statement.");
            stmt = conn.createStatement();
            System.out.println("Executing statement.");
            rset = stmt.executeQuery(args[1]);
            System.out.println("Results:");
            int numcols = rset.getMetaData().getColumnCount();
            while(rset.next()) {
                for(int i=1;i<=numcols;i++) {
                    System.out.print("\t" + rset.getString(i));
                }
                System.out.println("");
            }
        } catch(SQLException e) {
            e.printStackTrace();
        } finally {
            try { if (rset != null) rset.close(); } catch(Exception e) { }
            try { if (stmt != null) stmt.close(); } catch(Exception e) { }
            try { if (conn != null) conn.close(); } catch(Exception e) { }
        }
    }

    public static DataSource setupDataSource(String connectURI) {
        BasicDataSource ds = new BasicDataSource();
        ds.setDriverClassName("oracle.jdbc.driver.OracleDriver");
        ds.setUsername("scott");
        ds.setPassword("tiger");
        ds.setUrl(connectURI);
        return ds;
    }

    public static void printDataSourceStats(DataSource ds) {
        BasicDataSource bds = (BasicDataSource) ds;
        System.out.println("NumActive: " + bds.getNumActive());
        System.out.println("NumIdle: " + bds.getNumIdle());
    }

    public static void shutdownDataSource(DataSource ds) throws SQLException {
        BasicDataSource bds = (BasicDataSource) ds;
        bds.close();
    }
}

This ad is supporting your extension Smooth GesturesTurn off these ads?
用户在new 一个BasicDataSource时,并未真正建立连接池,建立连接是在用户首次使用getConnection获取连接时发生。
getConnection调用CreateDataSouce获取DataSource,其用synchronized修饰,保证同一时刻最多只有一个线程执行该段代码

如下:

  public Connection getConnection() throws SQLException {
        return createDataSource().getConnection();
    }
   protected synchronized DataSource createDataSource()
        throws SQLException {
        if (closed) {
            throw new SQLException("Data source is closed");
        }

        // Return the pool if we have already created it
        if (dataSource != null) {
            return (dataSource);
        }

        //  用户第一次使用getConnection请求连接时,建立数据库连接池
        ConnectionFactory driverCOnnectionFactory= createConnectionFactory();

        //建立连接池,使用commons-pool的GenericObjectPool
        createConnectionPool();

        // Set up statement pool, if desired
        GenericKeyedObjectPoolFactory statementPoolFactory = null;
        if (isPoolPreparedStatements()) {
            statementPoolFactory = new GenericKeyedObjectPoolFactory(null,
                        -1, // unlimited maxActive (per key)
                        GenericKeyedObjectPool.WHEN_EXHAUSTED_FAIL,
                        0, // maxWait
                        1, // maxIdle (per key)
                        maxOpenPreparedStatements);
        }

        // 设置连接池工厂
        createPoolableConnectionFactory(driverConnectionFactory, statementPoolFactory, abandonedConfig);

        // 建立数据库连接池实例
        createDataSourceInstance();
        
       // 根据配置,初始化建立一些数据库连接
        try {
            for (int i = 0 ; i 
    protected void createDataSourceInstance() throws SQLException {
        PoolingDataSource pds = new PoolingDataSource(connectionPool);
        pds.setAccessToUnderlyingConnectionAllowed(isAccessToUnderlyingConnectionAllowed());
        pds.setLogWriter(logWriter);
        dataSource = pds;
    }


PoolingDataSource封装数据库连接池,其实现了getConnection,如下:
  public Connection getConnection() throws SQLException {
        try {
            Connection cOnn= (Connection)(_pool.borrowObject());
            if (conn != null) {
                cOnn= new PoolGuardConnectionWrapper(conn);
            } 
            return conn;
        } catch(SQLException e) {
            throw e;
        } catch(NoSuchElementException e) {
            throw new SQLNestedException("Cannot get a connection, pool error " + e.getMessage(), e);
        } catch(RuntimeException e) {
            throw e;
        } catch(Exception e) {
            throw new SQLNestedException("Cannot get a connection, general error", e);
        }
    }

PoolableObjectFactory是生成连接池对象的工厂,其实现了makeObject,如下:

    public Object makeObject() throws Exception {
        Connection cOnn= _connFactory.createConnection();
        if (cOnn== null) {
            throw new IllegalStateException("Connection factory returned null from createConnection");
        }
        initializeConnection(conn);
        if(null != _stmtPoolFactory) {
            KeyedObjectPool stmtpool = _stmtPoolFactory.createPool();
            cOnn= new PoolingConnection(conn,stmtpool);
            stmtpool.setFactory((PoolingConnection)conn);
        }
        return new PoolableConnection(conn,_pool,_config);
    }   

PoolableConnection是连接代理,其实现了close,close并不真正关闭连接,而是调用_pool.returnObject(this)将连接还给对象池:

    public synchronized void close() throws SQLException {
        if (_closed) {
            // already closed
            return;
        }
        ......
        if (!isUnderlyingConectionClosed) {
            // Normal close: underlying connection is still open, so we
            // simply need to return this proxy to the pool
            try {
                _pool.returnObject(this); // XXX should be guarded to happen at most once
            } catch(IllegalStateException e) {
         ......
    }
上述borrowObject和returnObject都是GenericObjectPool提供的接口,分别用来从对象池中获取对象和归还对象。
getConnection序列图如下:


其它开源数据库连接池:C3P0、BoneCP、Proxool、阿里巴巴的Druid等
一些性能比较的文档:
c3p0、dbcp、proxool、BoneCP比较
DBCP,C3P0,Tomcat_JDBC 性能及稳定性测试
各种数据库连接池对比

reference:
commons-dbcp

推荐阅读
  • 在当前众多持久层框架中,MyBatis(前身为iBatis)凭借其轻量级、易用性和对SQL的直接支持,成为许多开发者的首选。本文将详细探讨MyBatis的核心概念、设计理念及其优势。 ... [详细]
  • 探讨如何真正掌握Java EE,包括所需技能、工具和实践经验。资深软件教学总监李刚分享了对毕业生简历中常见问题的看法,并提供了详尽的标准。 ... [详细]
  • 本文探讨了在Windows Server 2008环境下配置Tomcat使用80端口时遇到的问题,包括端口被占用、多项目访问失败等,并提供详细的解决方法和配置建议。 ... [详细]
  • 本文详细介绍了如何在Linux系统上安装和配置Smokeping,以实现对网络链路质量的实时监控。通过详细的步骤和必要的依赖包安装,确保用户能够顺利完成部署并优化其网络性能监控。 ... [详细]
  • PHP 5.2.5 安装与配置指南
    本文详细介绍了 PHP 5.2.5 的安装和配置步骤,帮助开发者解决常见的环境配置问题,特别是上传图片时遇到的错误。通过本教程,您可以顺利搭建并优化 PHP 运行环境。 ... [详细]
  • 本文详细介绍了 Dockerfile 的编写方法及其在网络配置中的应用,涵盖基础指令、镜像构建与发布流程,并深入探讨了 Docker 的默认网络、容器互联及自定义网络的实现。 ... [详细]
  • 作为一名新手,您可能会在初次尝试使用Eclipse进行Struts开发时遇到一些挑战。本文将为您提供详细的指导和解决方案,帮助您克服常见的配置和操作难题。 ... [详细]
  • 网络运维工程师负责确保企业IT基础设施的稳定运行,保障业务连续性和数据安全。他们需要具备多种技能,包括搭建和维护网络环境、监控系统性能、处理突发事件等。本文将探讨网络运维工程师的职业前景及其平均薪酬水平。 ... [详细]
  • 本文详细介绍了如何准备和安装 Eclipse 开发环境及其相关插件,包括 JDK、Tomcat、Struts 等组件的安装步骤及配置方法。 ... [详细]
  • 自己用过的一些比较有用的css3新属性【HTML】
    web前端|html教程自己用过的一些比较用的css3新属性web前端-html教程css3刚推出不久,虽然大多数的css3属性在很多流行的浏览器中不支持,但我个人觉得还是要尽量开 ... [详细]
  • 本文详细介绍如何利用已搭建的LAMP(Linux、Apache、MySQL、PHP)环境,快速创建一个基于WordPress的内容管理系统(CMS)。WordPress是一款流行的开源博客平台,适用于个人或小型团队使用。 ... [详细]
  • Struts与Spring框架的集成指南
    本文详细介绍了如何将Struts和Spring两个流行的Java Web开发框架进行整合,涵盖从环境配置到代码实现的具体步骤。 ... [详细]
  • 本文介绍如何将自定义项目设置为Tomcat的默认访问项目,使得通过IP地址访问时直接展示该自定义项目。提供了三种配置方法:修改项目路径、调整配置文件以及使用WAR包部署。 ... [详细]
  • 云计算的优势与应用场景
    本文详细探讨了云计算为企业和个人带来的多种优势,包括成本节约、安全性提升、灵活性增强等。同时介绍了云计算的五大核心特点,并结合实际案例进行分析。 ... [详细]
  • This pull request introduces the ability to provide comprehensive paragraph configurations directly within the Create Note and Create Paragraph REST endpoints, reducing the need for additional configuration calls. ... [详细]
author-avatar
亮我mc踢弯的
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有