JDBC——数据库连接池【C3P0、Druid】

5000 次连接数据库问题


package Test;

import java.sql.Connection;

/**
 * @Author: Gin
 * @Description: 传统方式创建 5000 次数据库连接
 * @Modified By: Gin
 * @Date: Created in 17:02 2021/9/14
 */
public class Test15 {
    public static void main(String[] args) {

        // 计算 5000 次连接数据库耗时多少
        long start = System.currentTimeMillis();
        System.out.println("开始连接:");
        for (int i = 0; i < 5000; i++){
            // 获取连接
            Connection connection = JDBCUtils.getConnection();
            // 做一些工作:比如得到 PreparedStatement,发送 SQL 语句等
            // ......
            // 关闭
            JDBCUtils.close(null, null, connection);
        }
        long end = System.currentTimeMillis();
        System.out.println("耗时:"+ (end - start) +" ms"); // 耗时:27190 ms
    }
}

在这里插入图片描述

  1. 传统的 JDBC 数据库连接使用 DriverManager 来获取,每次向数据库建立连接的时候都要将 Connection 加载到内存中,再验证 IP地址,用户名和密码(0.05s~1s时间) 。需要数据库连接的时候,就向数据库要求一个,频繁的进行数据库连接操作将占用很多的系统资源,容易造成服务器崩溃。
  2. 每一次数据库连接,使用完后都得断开,如果程序出现异常而未能关闭,将导致数据库内存泄漏,最终将导致重启数据库。
  3. 传统获取连接的方式,不能控制创建的连接数量,如连接过多,也可能导致内存泄漏,MySQL 崩溃。
  4. 解决传统开发中的数据库连接问题,可以采用 数据库连接池技术(connection pool)。

数据库连接池

数据库连接池基本介绍
  1. 预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。
  2. 数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。
  3. 当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中
  4. 在数据库连接池技术中,close() 不是真的断掉连接,而是把 connection 连接对象 放回连接池中。
  5. 画出示意图
    在这里插入图片描述
数据库连接池种类
  1. JDBC 的数据库连接池使用 javax.sql.DataSource 来表示,DataSource 只是一个接口,该接口通常由第三方提供实现
  2. C3P0 数据库连接池,速度相对较慢,稳定性不错(hibernate、spring)
  3. DBCP 数据库连接池,速度相对 C3P0 较快,但不稳定
  4. Proxool 数据库连接池,有监控连接池状态的功能,稳定性较 C3P0 差一点
  5. BoneCP 数据库连接池,速度快
  6. Druid(德鲁伊) 是阿里提供的数据库连接池,集 DBCP、C3PO、Proxool 优点于一身的数据库连接池【使用最多】

<!-- C3P0 依赖 -->
<dependency>
    <groupId>c3p0</groupId>
    <artifactId>c3p0</artifactId>
    <version>0.9.1.2</version>
</dependency>


通过 D3P0 方式获取一个数据库连接



import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.junit.Test;

import java.io.FileInputStream;
import java.sql.Connection;
import java.util.Properties;

/**
 * @Author: Gin
 * @Description:
 * @Modified By: Gin
 * @Date: Created in 18:41 2021/9/14
 */
public class Test0 {


    // 方式 1:相关参数,在程序中指定 url、user、password 等
    @Test
    public void testC3P0_1() throws Exception {
        // 1. 创建一个数据源对象
        ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
        // 2. 通过配置文件 mysql.properties 获取配置信息
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\mysql.properties"));
        // 读取相关参数
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String url = properties.getProperty("url");
        String driver = properties.getProperty("driver");
        // 给数据源 comboPooledDataSource 设置相关参数
        // 注意:连接管理是由 comboPooledDataSource 来管理的
        comboPooledDataSource.setDriverClass(driver);
        comboPooledDataSource.setJdbcUrl(url);
        comboPooledDataSource.setUser(user);
        comboPooledDataSource.setPassword(password);

        // 设置初始化连接数
        comboPooledDataSource.setInitialPoolSize(10);

        // 设置最大连接数
        comboPooledDataSource.setMaxPoolSize(50);

        // 获取一个连接
        // 这个方法就是从 DataSource 接口实现过来的
        Connection connection = comboPooledDataSource.getConnection();

        System.out.println("OK~");
        // 关闭连接
        connection.close();
    }
}



测试获取 5000 次连接的效率

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.junit.Test;

import java.io.FileInputStream;
import java.sql.Connection;
import java.util.Properties;

/**
 * @Author: Gin
 * @Description:
 * @Modified By: Gin
 * @Date: Created in 18:41 2021/9/14
 */
public class Test0 {


    // 方式 1:相关参数,在程序中指定 url、user、password 等
    @Test
    public void testC3P0_1() throws Exception {
        // 1. 创建一个数据源对象
        ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
        // 2. 通过配置文件 mysql.properties 获取配置信息
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\mysql.properties"));
        // 读取相关参数
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String url = properties.getProperty("url");
        String driver = properties.getProperty("driver");
        // 给数据源 comboPooledDataSource 设置相关参数
        // 注意:连接管理是由 comboPooledDataSource 来管理的
        comboPooledDataSource.setDriverClass(driver);
        comboPooledDataSource.setJdbcUrl(url);
        comboPooledDataSource.setUser(user);
        comboPooledDataSource.setPassword(password);

        // 设置初始化连接数
        comboPooledDataSource.setInitialPoolSize(10);

        // 设置最大连接数
        comboPooledDataSource.setMaxPoolSize(50);

        // 测试耗时多少毫秒
        System.out.println("开始连接:");
        long start = System.currentTimeMillis();
        for (int i = 0; i < 5000; i++) {
            // 获取一个连接
            // 这个方法就是从 DataSource 接口实现过来的
            Connection connection = comboPooledDataSource.getConnection();
            // 关闭连接
            connection.close();
        }
        long end = System.currentTimeMillis();
        System.out.println("耗时:"+ (end - start) + " ms"); // 耗时:860 ms
    }
}



在这里插入图片描述

相比于传统方式的 27190 ms,C3P0 数据库连接池连接方式效率要高得多

C3P0 的第二种方式:基于配置文件模板来完成
c3p0-config.xml【.xml文件名必须是这样】

<c3p0-config>
    <!-- 数据源名称,代表连接池 -->
    <named-config>Gin</named-config>
    <!--使用默认的配置读取数据库连接池对象 -->
    <default-config>
        <!--  连接参数 -->
        <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/db2?useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai&amp;rewriteBatchedStatements=true</property>
        <property name="user">root</property>
        <property name="password">root</property>

        <!-- 连接池参数 -->
        <!--初始化申请的连接数量-->
        <property name="initialPoolSize">10</property>
        <!--最大的连接数量-->
        <property name="maxPoolSize">50</property>
        <!-- 每次增长的连接数 -->
        <property name="acquireIncrement">5</property>
        <!-- 最小连接数 -->
        <property name="minPoolSize">5</property>
        <!-- 没每个连接对象可连接的最多的命令对象数 -->
        <property name="maxStatementsPerConnection">2</property>
        <!--超时时间-->
        <property name="checkoutTimeout">3000</property>
    </default-config>
</c3p0-config>



    // 第二种方式:使用配置文件模板来完成
    // 添加 C3P0 的 jar 包
    // 将 c3p0-config.xml 放在 src 目录下即可
    @Test
    public void testC3P0_2() throws SQLException {

        ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("Gin");

        // 测试连接 5000 次
        long start = System.currentTimeMillis();
        for (int i = 0; i < 5000; i++) {
            Connection connection = comboPooledDataSource.getConnection();
//            System.out.println("连接ok");
            connection.close();
        }
        long end = System.currentTimeMillis();
        System.out.println("耗时:"+ (end - start) +" ms"); // 耗时:990 ms
    }


德鲁伊连接池

druid 依赖

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.6</version>
</dependency>

druid.properties 配置文件


driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/db2?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai\
  &rewriteBatchedStatements=true
username=root
password=root
initialSize=10
minIdle=5
maxActive=50
maxWait=5000

用 Druid 实现获取 500000 次 MySQL 连接


package Test;

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

import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.util.Properties;

/**
 * @Author: Gin
 * @Description:
 * @Modified By: Gin
 * @Date: Created in 19:49 2021/9/14
 */
public class Test17 {

    @Test
    public void testDruid() throws Exception {
        // 添加 druid 的 jar 包
        // 在 src 目录下添加 druid.properties 文件
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\druid.properties"));

        // 创建一个指定参数的数据库连接池,Druid 连接池
        DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);

        // 测试 500000 次获取连接
        System.out.println("开始连接:");
        long start = System.currentTimeMillis();
        for (int i = 0; i < 500000; i++) {
            Connection connection = dataSource.getConnection();
            connection.close();
        }
        long end = System.currentTimeMillis();
        System.out.println("耗时:"+ (end - start) +" ms");

    }

}



连接量越大,Druid 的性能体现的越明显

使用 Druid 改进 封装数据库连接 操作



package Test;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.*;
import java.util.Properties;

/**
 * @Author: Gin
 * @Description:
 * @Modified By: Gin
 * @Date: Created in 20:20 2021/9/14
 */
public class JDBCUtilsByDruid {

    private static DataSource dataSource;

    // 在静态块中完成对 dataSource 的初始化
    static {
        Properties properties = new Properties();
        try{
            properties.load(new FileInputStream("src\\druid.properties"));
            dataSource = DruidDataSourceFactory.createDataSource(properties);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    // 获取 Connection 连接
    public static Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }

    // 放回连接
    public static void close(ResultSet resultSet, Statement statement, Connection connection){
        // 判断是否为空
        try{
            if(resultSet != null){
                resultSet.close();
            }
            if(statement != null){
                statement.close();
            }
            if(connection != null){
                connection.close();
            }
        }catch (Exception e){
            // 将编译异常转为运行异常
            throw new RuntimeException(e);
        }
    }

}



Test18 中测试通过 Druid 封装的数据库连接操作


package Test;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * @Author: Gin
 * @Description:
 * @Modified By: Gin
 * @Date: Created in 20:32 2021/9/14
 */
public class Test18 {

    // 测试 Druid 实现的 封装数据库操作
    public static void main(String[] args) {

        // 连接
        Connection conn = null;
        // SQL 语句
        String sql = "select `name`, password from admin where `name` = ?";
        PreparedStatement ps = null;
        ResultSet rs = null;

        try{
            // conn = JDBCUtils.getConnection();
            // 运行类型:class com.mysql.cj.jdbc.ConnectionImpl
            // System.out.println(conn.getClass());

            conn = JDBCUtilsByDruid.getConnection();
            // 运行类型:class com.alibaba.druid.pool.DruidPooledConnection
            System.out.println(conn.getClass());

            ps = conn.prepareStatement(sql);
            // 给问号赋值
            ps.setString(1, "Sherry");

            rs = ps.executeQuery();
            while(rs.next()){
//                String name = rs.getString("name");
//                String password = rs.getString("password");
                String name = rs.getString(1);
                String password = rs.getString(2);
                System.out.println(name + "\t" + password);
            }
        }catch(SQLException e){
            e.printStackTrace();
        }finally {
            JDBCUtilsByDruid.close(rs, ps, conn);
            // JDBCUtils.close(rs, ps, conn);
        }


    }
}



JDBCUtils 与 JDBCUtilsByDruid 的 close() 方法

JDBCUtils 的 connJDBCUtilsByDruid 的 conn 运行类型的不同可知:
JDBCUtils 的 connclose() 方法是调用了 MySQL 底层的关闭流方法
JDBCUtilsByDruid 的 connclose() 方法是调用的 Druid 底层的关闭方法
二者底层的具体实现不一样。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1 对于数据表的读操作 他可以把结果转换成List Array Set等java集合 便于程序员操作; 2 对于数据表的写操作 也变得很简单(只需写sql语句) 3 可以使用数据源 使用JNDI 数据库连接池等技术来优化性能 重用已经构建好的数据库连接对象 而不像php asp那样 费时费力的不断重复的构建和析构这样的对象 DBUtils包括3个包: org apache commons dbutils org apache commons dbutils handlers org apache commons dbutils wrappers DBUtils封装了对JDBC的操作 简化了JDBC操作 可以少写代码 org apache commons dbutils DbUtils 关闭链接等操作 QueryRunner 进行查询的操作 org apache commons dbutils handlers ArrayHandler :将ResultSet中第一行的数据转化成对象数组 ArrayListHandler将ResultSet中所有的数据转化成List List中存放的是Object[] BeanHandler :将ResultSet中第一行的数据转化成类对象 BeanListHandler :将ResultSet中所有的数据转化成List List中存放的是类对象 ColumnListHandler :将ResultSet中某一列的数据存成List List中存放的是Object对象 KeyedHandler :将ResultSet中存成映射 key为某一列对应为Map Map中存放的是数据 MapHandler :将ResultSet中第一行的数据存成Map映射 MapListHandler :将ResultSet中所有的数据存成List List中存放的是Map ScalarHandler :将ResultSet中一条记录的其中某一列的数据存成Object org apache commons dbutils wrappers SqlNullCheckedResultSet :对ResultSet进行操作 改版里面的值 StringTrimmedResultSet :去除ResultSet中中字段的左右空格 Trim ">1 对于数据表的读操作 他可以把结果转换成List Array Set等java集合 便于程序员操作; 2 对于数据表的写操作 也变得很简单(只需写sql语句) 3 可以使用数据源 使用JNDI 数据库连接池等技术来优化性能 重用已经构建好的 [更多]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值