从源码分析DBCP数据库连接池的原理

栏目: 数据库 · 发布时间: 5年前

内容简介:数据库连接池的概念大家应该都很熟悉,类似dbcp、c3p0,还有如今常用的druid等,使用时无非就是从网上抄几段配置,调一下参数,但是深入理解数据库连接池的原理在如今的Java开发中还是很有必要的,这里以dbcp为例,简要分析一下数据库连接池的实现原理这里我采用的版本是2.5.0版本我们先来看一段原生jdbc代码(省略了异常捕获):

数据库连接池的概念大家应该都很熟悉,类似dbcp、c3p0,还有如今常用的druid等,使用时无非就是从网上抄几段配置,调一下参数,但是深入理解数据库连接池的原理在如今的 Java 开发中还是很有必要的,这里以dbcp为例,简要分析一下数据库连接池的实现原理

这里我采用的版本是2.5.0版本

<dependency>
	    <groupId>org.apache.commons</groupId>
	    <artifactId>commons-dbcp2</artifactId>
	    <version>2.5.0</version>
	</dependency>
复制代码

分析

准备

我们先来看一段原生jdbc代码(省略了异常捕获):

Class.forName("com.mysql.cj.jdbc.Driver");
        String url = "jdbc:mysql://localhost:3306/xxxxxx";

        Connection conn = DriverManager.getConnection(url, "user", "password");
        Statement stat = conn.createStatement();
        ResultSet set = stat.executeQuery(sql);

        while(set.next()) {
            System.out.println(set.getString(2));
        }

        stat.close();
        conn.close();
复制代码

代码结构这里不关心,重点是其中的一句

Connection conn = DriverManager.getConnection(url, "user", "password");
复制代码

为什么要单独拿这一条出来说呢?因为我们使用数据库连接池也是调用了getConnection方法,在JDBC 2.0 API中,Java提倡使用DateSource接口来获取连接

我们现在的重点就是找到dhcp提供的DataSource接口,如果我们使用过dhcp配置,就一定见过这样的配置

spring:
  datasource:
    type: org.apache.commons.dbcp2.BasicDataSource
复制代码

我们就以BasicDataSource作为突破点,这个类实现了DataSource接口:

public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBeanRegistration, AutoCloseable
复制代码

既然我们要探究原理,就不要关注细枝末节的部分,我们直接点进最核心的getConnection方法,同时为了代码结构清晰,这里忽略所有异常捕获语句:

@Override
    public Connection getConnection() throws SQLException {
        if (Utils.IS_SECURITY_ENABLED) {
            final PrivilegedExceptionAction<Connection> action = new PaGetConnection();
            return AccessController.doPrivileged(action);
        }
        return createDataSource().getConnection();
    }
复制代码

整段代码很简单,根据是否开启安全管理,来选择获取连接的方式,我们这里只看没有安全管理的方式

数据源的创建

我们先来看createDataSource方法:

protected DataSource createDataSource() throws SQLException {
        if (closed) {
            throw new SQLException("Data source is closed");
        }
        // 如果数据源已存在,就直接返回
        if (dataSource != null) {
            return dataSource;
        }
        synchronized (this) {
        	// 使用双检锁的设计,保证dataSource是单例的
            if (dataSource != null) {
                return dataSource;
            }

			// 注册MBean,这里不是重点,可以暂时不关心
            jmxRegister();

            // 创建返回原生连接的工厂
            final ConnectionFactory driverConnectionFactory = createConnectionFactory();

            // 创建连接池工厂
            boolean success = false;
            PoolableConnectionFactory poolableConnectionFactory;
            try {
                poolableConnectionFactory = createPoolableConnectionFactory(driverConnectionFactory);
                poolableConnectionFactory.setPoolStatements(poolPreparedStatements);
                poolableConnectionFactory.setMaxOpenPreparedStatements(maxOpenPreparedStatements);
                success = true;
            } catch (final SQLException se) {
                throw se;
            } catch (final RuntimeException rte) {
                throw rte;
            } catch (final Exception ex) {
                throw new SQLException("Error creating connection factory", ex);
            }

            if (success) {
                // 创建连接池成功
                createConnectionPool(poolableConnectionFactory);
            }

            // 创建数据源池来管理连接
            DataSource newDataSource;
            success = false;
            try {
                newDataSource = createDataSourceInstance();
                newDataSource.setLogWriter(logWriter);
                success = true;
            } catch (final SQLException se) {
                throw se;
            } catch (final RuntimeException rte) {
                throw rte;
            } catch (final Exception ex) {
                throw new SQLException("Error creating datasource", ex);
            } finally {
                if (!success) {
                    closeConnectionPool();
                }
            }

            // 如果初始化连接数大于0,就进行预加载
            try {
                for (int i = 0; i < initialSize; i++) {
                    connectionPool.addObject();
                }
            } catch (final Exception e) {
                closeConnectionPool();
                throw new SQLException("Error preloading the connection pool", e);
            }

            // 如果空间连接回收器执行间隔时间大于0,则添加回收器任务
            startPoolMaintenance();

			// 返回数据源
            dataSource = newDataSource;
            return dataSource;
        }
    }
复制代码

代码看似很长,其实流程很简单:

  1. 判断能不能直接返回结果(数据源关闭或已存在),如果能,就直接返回
  2. 创建返回数据库连接的工厂
  3. 创建连接池工厂
  4. 数据库连接工厂 来包装 连接池工厂
  5. 创建数据库连接池
  6. 通过连接池来创建数据源实例
  7. 根据设置的参数,判断是否要进行额外操作(比如预加载数据库连接)
  8. 返回数据源

我们重点只有三个:数据库连接工厂是什么,连接池工厂是什么,连接池是什么。我们先来看数据库连接工厂

createConnectionFactory

这个方法的代码相比于上面的又有些混乱了,为了助于理解,我这里还是忽略了所有的异常捕获代码段:

protected ConnectionFactory createConnectionFactory() throws SQLException {
        // 加载JDBC驱动
        Driver driverToUse = this.driver;

        if (driverToUse == null) {
            Class<?> driverFromCCL = null;
            if (driverClassName != null) {
                if (driverClassLoader == null) {
                    driverFromCCL = Class.forName(driverClassName);
                } else {
                    driverFromCCL = Class.forName(driverClassName, true, driverClassLoader);
                }
            }

            if (driverFromCCL == null) {
            	// 这里的url就是我们与数据库建立连接的url
                driverToUse = DriverManager.getDriver(url);
            } else {
                driverToUse = (Driver) driverFromCCL.getConstructor().newInstance();
                if (!driverToUse.acceptsURL(url)) {
                    throw new SQLException("No suitable driver", "08001");
                }
            }
        }

        // 设置连接
        final String user = userName;
        if (user != null) {
            connectionProperties.put("user", user);
        } else {
            log("DBCP DataSource configured without a 'username'");
        }

        final String pwd = password;
        if (pwd != null) {
            connectionProperties.put("password", pwd);
        } else {
            log("DBCP DataSource configured without a 'password'");
        }

        final ConnectionFactory driverConnectionFactory = new DriverConnectionFactory(driverToUse, url,
                connectionProperties);
        return driverConnectionFactory;
    }
复制代码

这个方法做的事情很简单,就是单纯的加载驱动,真正创建连接工厂的是倒数第二行new DriverConnectionFactory这段代码,最终我们创建的也是DriverConnectionFactory类型对象,这里我们向这个工厂对象传入了数据库驱动、连接url、用户名和密码等属性

关于DriverConnectionFactory工厂类,这里就列一段其中的代码,我相信其他就不用多讲了

@Override
    public Connection createConnection() throws SQLException {
        return driver.connect(connectionString, properties);
    }
复制代码

PoolableConnectionFactory

接下来看这个连接池工厂类,但是我们并不准备先研究这个类,而是回到createDataSource方法中,发现有一个createConnectionPool方法,传入了PoolableConnectionFactory类对象,我们进入这个方法中:

protected void createConnectionPool(final PoolableConnectionFactory factory) {
        // 创建一个对象池
        final GenericObjectPoolConfig<PoolableConnection> config = new GenericObjectPoolConfig<>();
        
        updateJmxName(config);
        config.setJmxEnabled(registeredJmxObjectName != null);
        
        final GenericObjectPool<PoolableConnection> gop = createObjectPool(factory, config, abandonedConfig);
        gop.setMaxTotal(maxTotal);
        gop.setMaxIdle(maxIdle);
        gop.setMinIdle(minIdle);
        gop.setMaxWaitMillis(maxWaitMillis);
        gop.setTestOnCreate(testOnCreate);
        gop.setTestOnBorrow(testOnBorrow);
        gop.setTestOnReturn(testOnReturn);
        gop.setNumTestsPerEvictionRun(numTestsPerEvictionRun);
        gop.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
        gop.setSoftMinEvictableIdleTimeMillis(softMinEvictableIdleTimeMillis);
        gop.setTestWhileIdle(testWhileIdle);
        gop.setLifo(lifo);
        gop.setSwallowedExceptionListener(new SwallowedExceptionLogger(log, logExpiredConnections));
        gop.setEvictionPolicyClassName(evictionPolicyClassName);
        
        factory.setPool(gop);
        connectionPool = gop;
    }
复制代码
  1. 第一行创建了一个 对象池 ,Apache提供了四种对象池,GenericObjectPoolConfig只是其中一种,我们在本篇中暂且不关心其实现细节,只需要明白它是一个对象池就够了
  2. 接下来两行与Jmx有关,这里暂且不关心
  3. 接下来数行,通过我们提供的连接池和其他配置来创建一个对象池,同时设置变量的值,这些值都可以通过我们的配置文件来设置
  4. 最后我们再把连接池设置到PoolableConnectionFactory工厂类中,并将数据源本身的连接池设置为该连接池

现在明白我们为什么不直接研究PoolableConnectionFactory了吧,因为实际上我们调用的连接池是connectionPool

获取连接

刚才我们介绍了了连接工厂、连接池工厂和连接池,为了防止你忘记接下来要干什么,这里再把我们一开始研究的代码段放上来:

return createDataSource().getConnection();
复制代码

刚才只是研究了createDataSource方法,这个方法最终返回了一个数据源对象(这个数据源对象是通过connectionPool包装而来的PoolingDataSource<PoolableConnection>对象),我们接下来就要看真正获取连接的方法getConnection

这是一个抽象方法,因为createDataSource返回的是PoolingDataSource对象,所以我们就从这个对象中来分析,为了让代码结构清晰,这里依然略去了异常捕获代码:

@Override
    public Connection getConnection() throws SQLException {
        final C conn = pool.borrowObject();
        if (conn == null) {
            return null;
        }
        return new PoolGuardConnectionWrapper<>(conn);
    }
复制代码

我们先解决两个可能会有的问题:

  • C是Connection子类
  • PoolGuardConnectionWrapper是为了确保被关闭的连接不能被重用的包装连接类

现在应该就没有问题了,整个流程就是获取连接,如果获取不到就返回空,否则返回一个包装类。连接通过borrowObject方法来获取,这个方法的实现是在GenericObjectPool中(不知道为什么是这个类的一定是前面的代码没有认真看),关于这个类的实现方法就不是本篇的重点,在以后我会单独写一篇文章来介绍Apache提供的四种对象池

总结

最后依照惯例,来总结一下DBCP连接池获取连接的步骤,这里以调用getConnection方法获取连接为例分析:

  1. 判断数据源是否关闭或已存在,如果关闭则抛出异常,如果已存在则直接返回
  2. 创建可以返回数据库连接的工厂类,并加载数据库驱动
  3. 通过数据库连接工厂类,来创建一个连接池工厂类
  4. 通过连接池工厂类,创建数据库连接池并设置属性
  5. 通过连接池来创建数据源,并根据配置的属性来进行一些初始化操作
  6. 通过数据源中的对象池来获取对象(连接)

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

精通正则表达式

精通正则表达式

[美] Jeffrey E.F.Friedl / 余晟 / 电子工业出版社 / 2012-7 / 89.00元

《精通正则表达式(第3版)》内容简介:随着互联网的迅速发展,几乎所有工具软件和程序语言都支持的正则表达式也变得越来越强大和易于使用。《精通正则表达式(第3版)》是讲解正则表达式的经典之作。《精通正则表达式(第3版)》主要讲解了正则表达式的特性和流派、匹配原理、优化原则、实用诀窍以及调校措施,并详细介绍了正则表达式在Perl、Java、.NET、PHP中的用法。《精通正则表达式(第3版)》自第1版开......一起来看看 《精通正则表达式》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

SHA 加密
SHA 加密

SHA 加密工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具