数据库连接池


jdbc,在每次连接数据库的时候都需要去重新建立对象。可以使用一个连接池,每次使用完后归还给连接池。

连接池概述

连接池其实就是一个容器(集合),存放数据库连接的容器。

当系统初始化好后,容器被创建,容器中会申请一些连接对象,当用户来访问数据库时,从容器中获取连接对象,用户访问完之后,会将连接对象归还给容器。

使用连接池能节约资源使用户访问高效。

数据库连接池是一种复用 Connection 的组件,它可以避免反复创建新连接,提高 JDBC 代码的运行效率;


连接池实现

C3P0

  • https://sourceforge.net/projects/c3p0/

maven

<!-- https://mvnrepository.com/artifact/c3p0/c3p0 -->
<dependency>
    <groupId>c3p0</groupId>
    <artifactId>c3p0</artifactId>
    <version>0.9.1.2</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.25</version>
</dependency>

将 c3p0-config.xml 放到 src 目录下,并进行配置。

<c3p0-config>
    <default-config>
        <property name="user">root</property>
        <property name="password">toor</property>
        <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://192.168.1.1:3306/java?useUnicode=true&amp;serverTimezone=UTC&amp;characterEncoding=UTF-8</property>

        <property name="initialPoolSize">10</property>
        <!--连接池中保留的最大连接数 -->
        <property name="maxPoolSize">50</property>
        <property name="checkoutTimeout">3000</property>
    </default-config>
</c3p0-config>

Druid

  • https://github.com/alibaba/druid

简介

Druid 是阿里巴巴的一个开源项目,号称为监控而生的数据库连接池,在功能、性能、扩展性方面都超过其他,例如 DBCP、C3P0、BoneCP、Proxool、JBoss、DataSource 等连接池

Druid是一个JDBC组件,包含三个部分:

  • DruidDriver 代理Driver,能够提供基于Filter-Chain模式的插件体系。

  • DruidDataSource 高效管理的数据库连接池。

  • SQLParser SQL语法分析

直接把数据库密码写在配置文件中,容易导致安全问题。DruidDriver 和 DruidDataSource 都支持 PasswordCallback。

SQL Parser 是 Druid 的一个重要组成部分,它提供了 MySql、Oracle、Postgresql、SQL-92 的 SQL 的完整支持,这是一个手写的高性能 SQL Parser,支持 Visitor 模式,使得分析 SQL 的抽象语法树很方便。简单 SQL 语句用时 10 微秒以内,复杂 SQL 用时 30 微秒。

通过 Druid 提供的 SQL Parser 可以在 JDBC 层拦截 SQL 做相应处理,比如防御 SQL 注入(WallFilter)、合并统计没有参数化的 SQL(StatFilter 的 mergeSql)、SQL 格式化、分库分表。

maven

在 pom.xml 中引入 druid 官方提供的 Spring Boot Starter 组件

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.2.6</version>
</dependency>

导入 druid.properties 文件

driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://192.168.1.1:3306/mysql?characterEncoding=utf-8
username=root
password=toor
filters=stat
initialSize=2
maxActive=300
maxWait=60000
timeBetweenEvictionRunsMillis=60000
minEvictableIdleTimeMillis=300000
validationQuery=SELECT 1
testWhileIdle=true
testOnBorrow=false
testOnReturn=false
poolPreparedStatements=false
maxPoolPreparedStatementPerConnectionSize=200

初始化数据池测试

import com.alibaba.druid.pool.DruidDataSourceFactory;

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

public class test2 {
    public static void main(String[] args) throws Exception {
        Properties pro = new Properties();  //创建properties对象
        InputStream is = test2.class.getClassLoader().getResourceAsStream("druid.properties");   //获取配置文件资源
        pro.load(is);  //加载配置文件
        DataSource dataSource = DruidDataSourceFactory.createDataSource(pro);   //创建连接池对象,并传入配置文件信息
        Connection connection = dataSource.getConnection();    //获取连接对象
        System.out.println(connection);
    }
}

JDBCUtils 工具类

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;

public class JDBCUtils {
    private static DataSource ds;

    static {
        Properties pro = new Properties();
        InputStream rs = JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties");
        try {
            pro.load(rs);
            try {
                ds = DruidDataSourceFactory.createDataSource(pro);

            } catch (Exception e) {
                e.printStackTrace();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //返回连接对象
    public static Connection getConnection() throws SQLException {
        return ds.getConnection();
    }
    public  static void close(Statement stmt, Connection conn){
        if (stmt!=null){
            try {
                stmt.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }

        }
        if (conn!=null){
            try {
                conn.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }
    //该方法返回定义好的DataSource对象
    public static DataSource getDataSource(){
        return ds;
    }
}

查询测试

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

public class test2 {
    private static JDBCUtils JDBCutiss;

    public static void main(String[] args) throws Exception {

        Connection connection = JDBCutiss.getConnection();//获取连接对象
        String sql = "select * from login where username =?";  //设置sql语句
        PreparedStatement preparedStatement = connection.prepareStatement(sql);//传入sql语句并创建预编译执行对象
        preparedStatement.setString(1,"admin");  //sql语句设置值
        //        System.out.println(sql);
        ResultSet resultSet = preparedStatement.executeQuery();  //执行sql语句
        while (resultSet.next()){
            System.out.println(resultSet.getString("username"));//获取用户账户
            System.out.println(resultSet.getString("password"));//获取密码
        }
        JDBCutiss.close(preparedStatement,connection);
    }
}

HikariCP

  • https://github.com/brettwooldridge/HikariCP

maven

<dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
    <version>2.7.1</version>
</dependency>

创建一个DataSource实例,这个实例就是连接池

HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/test");
config.setUsername("root");
config.setPassword("password");
config.addDataSourceProperty("connectionTimeout", "1000"); // 连接超时:1秒
config.addDataSourceProperty("idleTimeout", "60000"); // 空闲超时:60秒
config.addDataSourceProperty("maximumPoolSize", "10"); // 最大连接数:10
DataSource ds = new HikariDataSource(config);

注意创建 DataSource 也是一个非常昂贵的操作,所以通常 DataSource 实例总是作为一个全局变量存储,并贯穿整个应用程序的生命周期。

有了连接池以后,我们如何使用它呢?和前面的代码类似,只是获取 Connection 时,把 DriverManage.getConnection() 改为 ds.getConnection():

try (Connection conn = ds.getConnection()) { // 在此获取连接
    ...
} // 在此“关闭”连接

通过连接池获取连接时,并不需要指定 JDBC 的相关 URL、用户名、口令等信息,因为这些信息已经存储在连接池内部了(创建 HikariDataSource 时传入的 HikariConfig 持有这些信息)。一开始,连接池内部并没有连接,所以,第一次调用 ds.getConnection(),会迫使连接池内部先创建一个 Connection,再返回给客户端使用。当我们调用 conn.close() 方法时(在 try(resource){...} 结束处),不是真正 “关闭” 连接,而是释放到连接池中,以便下次获取连接时能直接返回。

因此,连接池内部维护了若干个 Connection 实例,如果调用 ds.getConnection(),就选择一个空闲连接,并标记它为 “正在使用” 然后返回,如果对 Connection 调用 close(),那么就把连接再次标记为 “空闲” 从而等待下次调用。这样一来,我们就通过连接池维护了少量连接,但可以频繁地执行大量的 SQL 语句。

通常连接池提供了大量的参数可以配置,例如,维护的最小、最大活动连接数,指定一个连接在空闲一段时间后自动关闭等,需要根据应用程序的负载合理地配置这些参数。此外,大多数连接池都提供了详细的实时状态以便进行监控。


Source & Reference