zoukankan      html  css  js  c++  java
  • 数据库连接池技术详解

    前言

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

    数据库连接池技术概述

    所谓的数据库连接池技术,就是用来分配,管理,释放数据库连接的.你也许会问,好像我直接用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>

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

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

    资源重用

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

    更快的系统反应速度

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

    新的资源分配手段

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

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

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

    结语

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

    微信公众号:进击的程序狗
    邮箱:roobtyan@outlook.com
    个人博客:http://roobtyan.cn
    扫描下面的二维码关注我吧,你将收获到意想不到的东西哟……
    给大家准备了一份非常棒的JAVA的视频教程,从JAVA基础一直到JAVAWEB,还有非常强大的项目实战。
    就在我的微信公众号里,回复java就可查看,免费的呦!
    这里写图片描述

  • 相关阅读:
    C++---const
    qt--textEdit多行文本编辑框
    qt--QByteArray字节数组
    qt5--拖放
    qt5--自定义事件与事件的发送
    qt5--键盘事件
    qt5--鼠标事件
    qt5-事件过滤器
    qt5-event事件的传递
    qt-事件的接受和忽略
  • 原文地址:https://www.cnblogs.com/roobtyan/p/9576696.html
Copyright © 2011-2022 走看看