数据库连接池

1、数据库连接池的基本介绍

数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏。这项技术能明显提高对数据库操作的性能。数据库连接池其实就是一个容器(集合),存放着数据库连接。

连接池基本的思想是在系统初始化的时候,将数据库连接作为对象存储在内存中,当用户需要访问数据库时,并非建立一个新的连接,而是从连接池中取出一个已建立的空闲连接对象。使用完毕后,用户也并非将连接关闭,而是将连接放回连接池中,以供下一个请求访问使用。而连接的建立、断开都由连接池自身来管理。同时,还可以通过设置连接池的参数来控制连接池中的初始连接数、连接的上下限数以及每个连接的最大使用次数、最大空闲时间等等。也可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。

1.1、为什么要使用连接池

 一个数据库连接对象均对应一个物理数据库连接,每次操作都打开一个物理连接,使用完都关闭连接,这样造成系统的性能低下。使用数据库连接池可以节约资源,程序更加高效。

数据库连接池的解决方案是在应用程序启动时建立足够的数据库连接,并讲这些连接组成一个连接池(简单说:在一个“池”里放了好多半成品的数据库连接对象),由应用程序动态地对池中的连接进行申请、使用和释放。对于多于连接池中连接数的并发请求,应该在请求队列中排队等待。并且应用程序可以根据池中连接的使用率,动态增加或减少池中的连接数。连接池技术尽可能多地重用了消耗内存地资源,大大节省了内存,提高了服务器地服务效率,能够支持更多的客户服务。通过使用连接池,将大大提高程序运行效率,同时,我们可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。

1.2、Java中常见的数据连接池

在Java中开源的数据库连接池有以下几种 :

  1. C3P0:是一个开放源代码的JDBC连接池,它在lib目录中与Hibernate一起发布,包括了实现jdbc3和jdbc2扩展规范说明的Connection 和Statement 池的DataSources 对象。
  2. Druid:Druid不仅是一个数据库连接池,还包含一个ProxyDriver、一系列内置的JDBC组件库、一个SQL Parser。支持所有JDBC兼容的数据库,包括Oracle、MySql、Derby、Postgresql、SQL Server、H2等。
  3. Proxool:是一个Java SQL Driver驱动程序,提供了对选择的其它类型的驱动程序的连接池封装。可以非常简单的移植到现存的代码中,完全可配置,快速、成熟、健壮。可以透明地为现存的JDBC驱动程序增加连接池功能。
  4. DBCP:DBCP是一个依赖Jakarta commons-pool对象池机制的数据库连接池,DBCP可以直接的在应用程序中使用,Tomcat的数据源使用的就是DBCP。
我们在使用连接池时,连接池给我们提供了获取数据库连接的方法,但使用完成后还是要调用 close 方法来关闭连接。但是实际上使用连接池时调用 close() 方法实际上并没有关闭连接,只是将该连接归还给连接池。
 

2、C3P0连接池

C3P0是一个开源的JDBC连接池,它实现了数据源与JNDI绑定,支持JDBC3规范和实现了JDBC2的标准扩展说明的Connection和Statement池的DataSources对象。即将用于连接数据库的连接整合在一起形成一个随取随用的数据库连接池(Connection pool)。

2.1、基于maven项目使用C3P0

首先添加依赖:

<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>5.1.12</version>
</dependency>
<dependency>
  <groupId>com.mchange</groupId>
  <artifactId>c3p0</artifactId>
  <version>0.9.5.2</version>
</dependency>

因为使用的是 mysql,所以我们要添加 mysql 的驱动包依赖。

如果新建的 maven 项目是 java SE 的项目,我们需要手动建一个 resource 包,并且标记为 resource folder,因为我们需要将 cp30 的配置文件放在该包下。

新建 cp30 的配置文件 c3p0-config.xml,并将该配置文件放在 resource 包下。只有放在 resource 包下,编译过后你才会发现 cp30 的配置文件生成在 target 的classes 文件夹下。最终目录如下:

c3p0-config.xml 文件内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
    <!--使用默认的配置读取连接池对象-->
    <default-config>
        <!--连接参数-->
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/db_test</property>
        <property name="user">root</property>
        <property name="password">123456</property>

        <!--连接池参数-->
        <!--初始化申请的连接数量。在初始化时就申请的连接数-->
        <property name="initialPoolSize">5</property>
        <!--超时时间-->
        <property name="checkoutTimeout">3000</property>
        <property name="maxIdleTime">30</property>
        <!--最大的连接数量。超过该数目不会再申请多的连接-->
        <property name="maxPoolSize">10</property>
        <property name="minPoolSize">5</property>
        <property name="maxStatements">200</property>
    </default-config>

    <!--配置连接池mysql-->
    <named-config name="mysql">
        <property name="acquireIncrement">50</property>
        <property name="initialPoolSize">100</property>
        <property name="minPoolSize">50</property>
        <property name="maxPoolSize">1000</property><!-- intergalactoApp adopts a different approach to configuring statement caching -->
        <property name="maxStatements">0</property>
        <property name="maxStatementsPerConnection">5</property>
    </named-config>

    <!--配置连接池2-->
    <!--......-->
</c3p0-config>

在配置文件中可以配置多个连接池,default-config 是默认的连接池配置,可以通过 name-config 标签来配置其他的连接池配置,通过 name 属性为该配置指定名称。在使用额外的连接池配置时,最终的配置信息是默认配置和该额外的配置的并集,并且以额外的配置优先。

在获取连接池对象时,如果不传参则使用的是默认配置,也可以通过使用 name 参数来使用指定配置。代码如下,我们测试一下连接池是否配置成功:

public class CP30_test {
    public void test01() throws SQLException {
        //1.创建数据库连接池对象。不指定名称使用的是默认配置,即default-config
        DataSource ds = new ComboPooledDataSource();
//可以通过参数来使用指定的配置 //DataSource ds = new ComboPooledDataSource("mysql-config"); //2.获取连接对象 Connection conn = ds.getConnection(); //3.打印 System.out.println(conn); } }

打印结果如下:

说明配置已经成功。我们可以通过该连接池来获取连接对象,JDBC 的其他步骤跟不使用连接池时一样。

2.1.1、新建C3P0Util工具类

一般在使用数据库连接池时,我们会新建一个工具类来方便我们使用连接池。

工具类代码示例:

package com.c3p0.utils;
 
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
 
import javax.sql.DataSource;
 
import com.mchange.v2.c3p0.ComboPooledDataSource;
 
public class C3P0Util {
    //使用ComboPooledDataSource来生成DataSource的实例
    private static DataSource dataSource =new ComboPooledDataSource();
 
    //从连接池中获取连接
    public static Connection getConnection() {
        try {
            return dataSource.getConnection();
        }catch (SQLException e) {
            // TODO Auto-generated catch block
            throw new RuntimeException();
        }
    }
 
    //释放连接回连接池
    public static void release(Connection conn, Statement stmt, ResultSet rs) {
        if (rs !=null) {
            try {
                rs.close();
            }catch (Exception e) {
                e.printStackTrace();
            }
            rs =null;
        }
        if (stmt !=null) {
            try {
                stmt.close();
            }catch (Exception e) {
                e.printStackTrace();
            }
            stmt =null;
        }
        if (conn !=null) {
            try {
                conn.close();
            }catch (Exception e) {
                e.printStackTrace();
            }
            conn =null;
        }
    }
}

使用工具类:

public class TestCRUD {
    public void testInsert() {
        Connection conn =null;
        PreparedStatement ps =null;
        conn = C3P0Util.getConnection();
        try {
            ps = conn.prepareStatement("INSERT INTO users (username,PASSWORD,email,birthday)VALUES('SUN99','123','123456@qq.com','2020-01-01')");
            ps.executeUpdate();
            System.out.println("添加操作执行成功!");
        }catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            System.out.println("添加操作执行失败!");
        }finally {
            C3P0Util.release(conn, ps,null);
        }
    }
 }

2.2、c3p0配置参数详解

常见配置参数:

  • initialPoolSize:初始化连接数。在容器初始化时就申请的连接数量

  • maxPoolSize:最大连接数。如果需要的连接超过该数目容器也不会再申请多的连接,此时数据库连接可能就会超时,无法查询到数据。最大连接数应该根据服务器的性能来灵活配置

<c3p0-config>   
    <default-config>   
    <!--当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default:3 -->   
    <property name="acquireIncrement">3</property>   
 
    <!--定义在从数据库获取新连接失败后重复尝试的次数。Default:30 -->   
    <property name="acquireRetryAttempts">30</property>   
       
    <!--两次连接中间隔时间,单位毫秒。Default:1000 -->   
    <property name="acquireRetryDelay">1000</property>   
       
    <!--连接关闭时默认将所有未提交的操作回滚。Default:false -->   
    <property name="autoCommitOnClose">false</property>   
       
    <!--c3p0将建一张名为Test的空表,并使用其自带的查询语句进行测试。如果定义了这个参数那么   
    属性preferredTestQuery将被忽略。你不能在这张Test表上进行任何操作,它将只供c3p0测试   
    使用。Default:null-->   
    <property name="automaticTestTable">Test</property>   
       
    <!--获取连接失败将会引起所有等待连接池来获取连接的线程抛出异常。但是数据源仍有效   
    保留,并在下次调用getConnection()的时候继续尝试获取连接。如果设为true,那么在尝试   
    获取连接失败后该数据源将申明已断开并永久关闭。Default:false-->   
    <property name="breakAfterAcquireFailure">false</property>   
       
    <!--当连接池用完时客户端调用getConnection()后等待获取新连接的时间,超时后将抛出   
    SQLException,如设为0则无限期等待。单位毫秒。Default:0 -->   
    <property name="checkoutTimeout">100</property>   
       
    <!--通过实现ConnectionTester或QueryConnectionTester的类来测试连接。类名需制定全路径。   
    Default: com.mchange.v2.c3p0.impl.DefaultConnectionTester-->   
    <property name="connectionTesterClassName"></property>   
       
    <!--指定c3p0 libraries的路径,如果(通常都是这样)在本地即可获得那么无需设置,默认null即可   
    Default:null-->   
    <property name="factoryClassLocation">null</property>   
       
    <!--强烈不建议使用该方法,将这个设置为true可能会导致一些微妙而奇怪的bug-->   
    <property name="forceIgnoreUnresolvedTransactions">false</property>   
       
    <!--每60秒检查所有连接池中的空闲连接。Default:0 -->   
    <property name="idleConnectionTestPeriod">60</property>   
       
    <!--初始化时获取三个连接,取值应在minPoolSize与maxPoolSize之间。Default:3 -->   
    <property name="initialPoolSize">3</property>   
       
    <!--最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃。Default:0 -->   
    <property name="maxIdleTime">60</property>   
       
    <!--连接池中保留的最大连接数。Default:15 -->   
    <property name="maxPoolSize">15</property>   
       
    <!--JDBC的标准参数,用以控制数据源内加载的PreparedStatements数量。但由于预缓存的statements   
    属于单个connection而不是整个连接池。所以设置这个参数需要考虑到多方面的因素。   
    如果maxStatements与maxStatementsPerConnection均为0,则缓存被关闭。Default:0-->   
    <property name="maxStatements">100</property>   
       
    <!--maxStatementsPerConnection定义了连接池内单个连接所拥有的最大缓存statements数。Default:0 -->   
    <property name="maxStatementsPerConnection"></property>   
       
    <!--c3p0是异步操作的,缓慢的JDBC操作通过帮助进程完成。扩展这些操作可以有效的提升性能   
    通过多线程实现多个操作同时被执行。Default:3-->   
    <property name="numHelperThreads">3</property>   
       
    <!--当用户调用getConnection()时使root用户成为去获取连接的用户。主要用于连接池连接非c3p0   
    的数据源时。Default:null-->   
    <property name="overrideDefaultUser">root</property>   
       
    <!--与overrideDefaultUser参数对应使用的一个参数。Default:null-->   
    <property name="overrideDefaultPassword">password</property>   
       
    <!--密码。Default:null-->   
    <property name="password"></property>   
       
    <!--定义所有连接测试都执行的测试语句。在使用连接测试的情况下这个一显著提高测试速度。注意:   
    测试的表必须在初始数据源的时候就存在。Default:null-->   
    <property name="preferredTestQuery">select id from test where id=1</property>   
       
    <!--用户修改系统配置参数执行前最多等待300秒。Default:300 -->   
    <property name="propertyCycle">300</property>   
       
    <!--因性能消耗大请只在需要的时候使用它。如果设为true那么在每个connection提交的   
    时候都将校验其有效性。建议使用idleConnectionTestPeriod或automaticTestTable   
    等方法来提升连接测试的性能。Default:false -->   
    <property name="testConnectionOnCheckout">false</property>   
       
    <!--如果设为true那么在取得连接的同时将校验连接的有效性。Default:false -->   
    <property name="testConnectionOnCheckin">true</property>   
       
    <!--用户名。Default:null-->   
    <property name="user">root</property>   
       
    <!--早期的c3p0版本对JDBC接口采用动态反射代理。在早期版本用途广泛的情况下这个参数   
    允许用户恢复到动态反射代理以解决不稳定的故障。最新的非反射代理更快并且已经开始   
    广泛的被使用,所以这个参数未必有用。现在原先的动态反射与新的非反射代理同时受到   
    支持,但今后可能的版本可能不支持动态反射代理。Default:false-->   
    <property name="usesTraditionalReflectiveProxies">false</property> 
    </default-config>      
</c3p0-config>

3、druid连接池

3.1、druid连接池的基本介绍

Druid是阿里开源的数据库连接池,作为后起之秀,性能比dbcp、c3p0更高,使用也越来越广泛。Druid不仅是一个数据库连接池,还包含一个ProxyDriver、一系列内置的JDBC组件库、一个SQL Parser。支持所有JDBC兼容的数据库,包括Oracle、MySql、Derby、Postgresql、SQL Server、H2等。

druid的优点:

  1. 高性能。性能比dbcp、c3p0高很多。
  2. 只要是jdbc支持的数据库,druid都支持,对数据库的支持性好。并且Druid针对oracle、mysql做了特别优化。
  3. 提供监控功能。可以监控sql语句的执行时间、ResultSet持有时间、返回行数、更新行数、错误次数、错误堆栈等信息,来了解连接池、sql语句的工作情况,方便统计、分析SQL的执行性能

3.2、基于maven项目使用druid连接池

首先添加依赖,因为使用的是 mysql,所以也要添加 mysql 的驱动依赖:

<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>druid</artifactId>
  <version>1.1.8</version>
</dependency>
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>5.1.12</version>
</dependency>

依赖添加完成,我们需要添加 druid 的配置文件。如果新建的 maven 项目是 java SE 的项目,我们需要手动建一个 resource 包,并且该文件应该被标记为 resource folder,因为我们需要将 cp30 的配置文件放在该包下。

新建 druid 的配置文件 druid.properties(可自定义文件名称),并将该配置文件放在 resource 包下(只有放在 resource 包下,编译过后你才会发现 cp30 的配置文件生成在 target 的classes 文件夹下)。最终目录如下:

druid.properties 配置文件的内容如下:

url=jdbc:mysql://localhost:3306/test
#这个可以缺省的,会根据url自动识别
driverClassName=com.mysql.jdbc.Driver
username=root
password=123456

##初始连接数,默认0
initialSize=10
#最大连接数,默认8
maxActive=30
#最小闲置数
minIdle=10
#获取连接的最大等待时间,单位毫秒
maxWait=2000
#缓存PreparedStatement,默认false
poolPreparedStatements=true
#缓存PreparedStatement的最大数量,默认-1(不缓存)。大于0时会自动开启缓存PreparedStatement,所以可以省略上一句设置
maxOpenPreparedStatements=20

此时我们就可以通过连接池来获取数据库连接了:

package org.example;

import com.alibaba.druid.pool.DruidDataSourceFactory;
import org.junit.Test;

import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Properties;

public class DruidTest {
    
    @Test
    public void test01() {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        try {
            //数据源配置
            Properties properties=new Properties();
            //通过当前类的class对象获取资源文件
            InputStream is = DruidTest.class.getResourceAsStream("/druid.properties");
            properties.load(is);

            //返回的是DataSource,不是DruidDataSource
            DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);

            //获取连接
            connection = dataSource.getConnection();

            //PreparedStatement接口
            String sql = "update user set name = 'newName' where id = 2";
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.execute();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(preparedStatement != null) {
                try {
                    preparedStatement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }

           //关闭连接。实际上是将连接归还给连接池
            try {
                if(connection != null) {
                    connection.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

    }
}

3.2.1、建立druid工具类

一般在使用数据库连接池时,我们会新建一个工具类来方便我们使用连接池。下面我们建立一个 druid 工具类,在该类中实现多个方法封装以我们方便使用,包括:获取数据库连接池、获取数据库的连接、关闭数据库连接资源。

代码如下:

package org.example;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;

public class DruidUtil {
    private static DataSource ds;

    static {
        //加载配置文件和建立连接池
        try {
            Properties pro = new Properties();
            InputStream resourceAsStream = DruidUtil.class.getClassLoader().getResourceAsStream("Druid.properties");
            pro.load(resourceAsStream);
            ds = DruidDataSourceFactory.createDataSource(pro);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取数据库连接池
     * @return
     */
    public static DataSource getDataSource(){
        return ds;
    }


    /**
     * 获取连接池中的一个连接
     * @return
     * @throws SQLException
     */
    public static Connection getConnection() throws SQLException {
        return ds.getConnection();
    }


    /**
     * 关闭数据库的资源  三个对象都存在时
     * @param conn
     * @param res
     * @param pstmt
     */
    public static void close(Connection conn, ResultSet res, PreparedStatement pstmt){
        if (conn!=null){
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (res!=null){
            try {
                res.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (pstmt!=null){
            try {
                pstmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 关闭数据库的连接(只存在Connection和PreparedStatement对象时)
     * @param conn
     * @param pstmt
     */
    public void close(Connection conn,PreparedStatement pstmt){
        if (conn!=null){
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (pstmt!=null){
            try {
                pstmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

我们就可以使用 druid 工具类来获取数据库连接、关闭资源。

实例代码如下:

public class DruidTest {
    public void utilTest() {
        Connection connection = null;
        ResultSet resultSet = null;
        PreparedStatement preparedStatement = null;
        try {
            connection = DruidUtil.getConnection();   //获取数据库连接

            String sql = "select * from user";
            preparedStatement = connection.prepareStatement(sql);
            resultSet = preparedStatement.executeQuery();
            while (resultSet.next()){
                Integer id = resultSet.getInt("id");
                String name = resultSet.getString("name");
                String password = resultSet.getString("password");
                System.out.println(id+" "+name+" "+password);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DruidUtil.close(connection,resultSet,preparedStatement);   //释放资源
        }
    }
}
原文地址:https://www.cnblogs.com/wenxuehai/p/15058811.html