数据库连接池技术详解

Posted 最高权限比特流

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据库连接池技术详解相关的知识,希望对你有一定的参考价值。

前言

今天来讲一下数据库连接池技术.其实这个名词也就是听起来高大上一点,实际上并不是很复杂的内容,相信在我的讲解下,并且自己实际的将代码写一遍之后,能够对这项技术有较为深刻的理解.废话不多说,开始讲解.

数据库连接池技术概述

所谓的数据库连接池技术,就是用来分配,管理,释放数据库连接的.你也许会问,好像我直接用JDBC也能够实现这些功能吧.
嗯,你说的没错,JDBC确实也可以,但是,你记不记得,我们使用JDBC技术的时候,每次用完了,是不是都会将连接关闭;等到下一次再用的时候,是不是都得将数据库连接再打开?
实际上,数据库链接资源是十分宝贵的,我们在小型的项目中还看不出来,在高并发的项目中,你会发现,这样频繁的打开和关闭数据库链接是对服务器的一种摧残,十分影响效率.
那么,数据库连接池是如何做的呢?
实现思路是这样的:在每次有访问的时候,数据库连接池会给用户分配一个数据库连接,当用户用完了连接之后,连接池再将连接回收,放回一个连接集合中.
原理就是这样的,我们来看一下这张图加深印象

这里写图片描述

这样你可能还是不太清楚,而且,数据库连接池要考虑的东西要比上面说的更复杂,不过不要害怕,我通过实际的代码来帮你理解一下.
备注:下面的代码是自己实现一个简单的数据库连接池,着重了解数据库连接池的原理.


自己实现一个数据库连接池

注意:下面的代码我是分块讲解的,你一个个粘贴下来,最后肯定也是能运行的,为了方便,我会在讲完之后,给出完整的实现代码.

  • 定义初始化连接数目,最大连接数以及当前已经连接的数目
    一开始,当数据库连接池启动的时候,为了实现上面的需求,我们肯定是要先给出几个已经完成的连接的,这样用户访问的时候就能直接拿到了;此外,当某一段时间的访问用户超过我定义的连接池中的连接个数,肯定是要额外新建连接给用户使用;当然,这个新建的连接肯定是不能无限制的,否则还是会很影响效率的,所以,我们还要定义最大的连接数.

private final int init_count = 3//初始化链接数目
private final int max_count = 6//最大连接数
private int current_count = 0//到当前连接数
  • 那么,我们一开始新建的连接池要放在哪里供用户使用呢?肯定是要创建一个连接集合的,这样的操作比较方便.至于为什么要使用LinkedList这样一个集合,一会就会介绍的.

private LinkedList<Connection> pool = new LinkedList<Connection>();
  • 刚才说了,一开始我们肯定是要初始化连接给用户使用的,那么,就需要在连接池启动的时候就新建一定数量的链接.分析后发现,放在构造函数中初始化链接是最好不过的了,最后再将连接放在链接集合中.

 //构造函数,初始化链接放入连接池
 public MyPool() {
     for (int i=0;i<init_count;i++){
         //记录当前连接数
         current_count++;
         //createConnection是自定义的创建链接函数.
         Connection connection = createConnection();
         pool.addLast(connection);
     }
 }
  • 创建一个新的连接,这个就没啥好说的了,毕竟你要的链接都是从这来的.

public Connection createConnection() {
    Class.forName("com.mysql.jdbc.Driver");
    Connection connection =DriverManager.getConnection("jdbc:mysql://localhost:3306/keyan","root","root");
    return connection;

  • 获取链接
    当用户来访问的时候,我们肯定是要给用户一个连接的,如果池中没有连接了(所有连接均被占用),那么就要创建新的连接,使用createConnection()函数,当然,这个连接的个数肯定是不能超过最大连接数的.如果不满足这两个条件,那么直接抛出异常.

public Connection getConnection() {
     if (pool.size() > 0){
         //removeFirst删除第一个并且返回
         //现在你一定看懂了我说的为什要用LinkedList了吧,因为下面的这个
         //removeFirst()方法会将集合中的第一个元素删除,但是还会返回第一个元素
         //这样就省去了我们很多不必要的麻烦
         return pool.removeFirst();
     }
     if (current_count < max_count){
         //记录当前使用的连接数
         current_count++;
         //创建链接
         return createConnection();
     }
     throw new RuntimeException("当前链接已经达到最大连接数");
}
  • 释放资源
    当用户使用完了连接之后,我们要做的并不是关闭连接,而是将连接重新放入资源池LinkedList之中,这样就省去了一遍又一遍的连接关闭.这个就是连接池的核心内容.是不是很简单?

 public void releaseConnection(Connection connection){
    if (pool.size() < init_count){
        pool.addLast(connection);
        current_count--;
    }else {
        try {
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

整个的实现过程就是这样的,下面我把全部的代码贴出来,方便大家学习.

//单元测试
@Test
public class MyPool {
    private final int init_count = 3//初始化链接数目
    private final int max_count = 6//最大
    private int current_count = 0//到当前连接数
    //连接池,用来存放初始化链接
    private LinkedList<Connection> pool = new LinkedList<Connection>();

    //构造函数,初始化链接放入连接池
    public MyPool() {
        for (int i=0;i<init_count;i++){
            //记录当前连接数
            current_count++;
            Connection connection = createConnection();
            pool.addLast(connection);
        }
    }

    //创建新的连接
    public Connection createConnection() {
        try {
            Class.forName("com.mysql.jdbc.Driver");
            Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/keyan","root","root");
            return connection;
        }catch (Exception e){
            System.out.println("数据库链接异常");
            throw new RuntimeException();
        }
    }

    //获取链接
    public Connection getConnection() {
        if (pool.size() > 0){
            //removeFirst删除第一个并且返回
            return pool.removeFirst();
        }
        if (current_count < max_count){
            //记录当前使用的连接数
            current_count++;
            //创建链接
            return createConnection();
        }
        throw new RuntimeException("当前链接已经达到最大连接数");
    }

    //释放链接
    public void releaseConnection(Connection connection){
        if (pool.size() < init_count){
            pool.addLast(connection);
            current_count--;
        }else {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

自己跑了一遍代码之后,你是不是发现原来看起来很复杂的技术,并不像我们想得那样?
好了,介绍完了基本的数据库连接池技术原理之后,我们就要介绍两个开源的优秀的数据连接池技术.
其实,真正的数据库连接池要考虑的东西比我们刚才写的这玩意复杂,现阶段不需要我们写这样复杂的东西,不过如果你感兴趣的话,可以看看数据库连接池的源代码--没错,这两个连接池都是开源的.OK,接下来就开始吧!

优秀的数据库连接池

首先,sun公司规定,连接池技术需实现javax.sql.DataSource接口,也就是说,如果你要自己实现数据库连接池,那么就必须实现这个接口.是不是很牛比的样子,实际上,作为标准制定方,sun公司还是有很多要求的,这是作为标准制定者的权利,所以,企业或者国家往往会对一些关键技术标准的制定打得头破血流.额,扯远了...

DBPC

其实,你可能不知道,如果你的tomcat经过特殊的配置,也是可以作为数据库连接池使用的,因为tomcat内置的就是DBPC..
想要使用DBPC,你必须导入三个jar文件:commons-dbcp2-2.2.0.jar,commons-pool2-2.5.0.jar,commons-logging-1.2.jar.我想,以你的聪明才智,想要获取这三个jar文件,一定是小菜一叠,这是一个软件开发者的必备技能--学会如何搜索.
使用起来就非常简单了.使用的方式主要有两种,一种是硬编码的方式,就是自己手动设置各种参数,另外一种就是配置相应的配置文件,然后载入就行了.

硬编码实现DBPC

BasicDataSource dataSource = new BasicDataSource();
//参数配置:初始化连接数,最大连接数,连接字符串,驱动,用户,密码
dataSource.setInitialSize(3);   //最大初始化链接
dataSource.setMaxTotal(6);      //最大链接
dataSource.setMaxIdle(3000);    //最大空闲时间
dataSource.setUrl("jdbc:mysql:///keyan");   //url
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUsername("root");
dataSource.setPassword("root");

//获取链接
Connection connection = dataSource.getConnection();
connection.prepareStatement("SELECT * FROM e_person").execute();
connection.close();

使用查询的时候,只需要按照原来JDBC的操作方式就行了,这个没啥好说的,按照我上面的配置方式实现就行了,一定不要忘记导入包.

配置文件方式实现

//创建properties配置文件
Properties properties = new Properties();
//获取文件流
InputStream in = DBCPTest.class.getResourceAsStream("db.properties");
//加载配置文件
properties.load(in);
//创建数据源对象
BasicDataSource dataSource = BasicDataSourceFactory.createDataSource(properties);

//获取链接
Connection connection = dataSource.getConnection();
ResultSet resultSet = connection.prepareStatement("SELECT * FROM e_person").executeQuery();
while (resultSet.next()){
   System.out.println(resultSet.getString("work_name"));
}
connection.close();

上面是基本的操作流程,下面我们看一下这个xml文件的配置
文件db.properties

url=jdbc:mysql:///keyan
driverClassName=com.mysql.jdbc.Driver
username=root
password=root
initialSize=3
maxActive=6
maxIdle=3000

特别要注意的一点是,这个配置文件一定要放在和你的这DBPC类放在同一个包下.
都完成了之后,直接用就可以了.

C3P0

c3p0同样是非常优秀的连接池技术,这个需要导入的文件比较少,只有一个c3p0-0.9.1.2.jar.自己去网站上找一下就好了.
使用c3p0同样是有两种方式,分别也是硬编码方式和配置文件方式.

硬编码方式

//配置相关的参数
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setJdbcUrl("jdbc:mysql:///keyan");
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setUser("root");
dataSource.setPassword("root");
dataSource.setInitialPoolSize(3);
dataSource.setMaxPoolSize(6);
dataSource.setMaxIdleTime(1000);

Connection connection = dataSource.getConnection();
//sql语句
ResultSet resultSet = connection.prepareStatement("SELECT * FROM e_project").executeQuery();
while (resultSet.next()){
   System.out.println(resultSet.getString("project_name"));
}
connection.close();

和上面一样,简单的使用一下就行了,知道如何使用就行.至于更加深层次的东西,有兴趣的话慢慢研究吧.

配置文件方式

ComboPooledDataSource dataSource = new ComboPooledDataSource();
Connection connection = dataSource.getConnection();
ResultSet resultSet = connection.prepareStatement("SELECT * FROM e_project").executeQuery();
while (resultSet.next()){
    System.out.println(resultSet.getString("project_name"));
}
connection.close();

乍一看,你可能会问,不是配置文件方式实现吗?没错,只是这个是隐式的调用,不需要你实现,只需要新建一个ComboPooledDataSource对象就行了,默认调用xml文件,文件路径是src根目录.也就是说,你只需要将配置文件写在src目录下就行了.重点是如何写这个xml文件.
注意,文件名c3p0-config.xml,这个文件名不能改,必须是这个,文件路径必须是src根目录,否则读取不到.

<c3p0-config>
    <default-config>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/test</property>
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="user">root</property>
        <property name="password">root</property>
        <property name="initialPoolSize">3</property>
        <property name="maxPoolSize">6</property>
        <property name="maxIdleTime">1000</property>
    </default-config>
</c3p0-config>

配置文件的参数大体上用到的就这么多,当然,你也可以去找一下相关的资料,了解一下更加详细的参数意义,这些参数,我么日常使用足够了.

补充内容:数据库连接池的优点

资源重用

由于数据库连接得以重用,避免了频繁创建,释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增加了系统运行环境的平稳性。

更快的系统反应速度

数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于连接池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而减少了系统的响应时间

新的资源分配手段

对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接池的配置,实现某一应用最大可用数据库连接数的限制,避免某一应用独占所有的数据库资源

统一的连接管理,避免数据库连接泄露:

在较为完善的数据库连接池实现中,可根据预先的占用超时设定,强制回收被占用连接,从而避免了常规数据库连接操作中可能出现的资源泄露

结语

感谢您的阅读,欢迎指正博客中存在的问题,也可以跟我联系,一起进步,一起交流!


以上是关于数据库连接池技术详解的主要内容,如果未能解决你的问题,请参考以下文章

有人熟悉阿里的 druid 的连接池么

Java线程池详解

Spring Boot 使用 Druid 连接池详解

Java数据库连接池详解

Java中数据库连接池原理机制详解面试+提高

连接池详解,c3p0与dbcp的区别!