JDBC连接池Hikaricp

Hikaricp简介

HikariCP 是一个快速、简单、可靠的 JDBC 连接池,在性能上做了很多优化,是目前最快的数据库连接池;

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

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

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

快速入门

Cache分为LoadingCache(同步缓存),AsyncLoadingCache(异步缓存)。

pom 依赖

    <dependency>
      <groupId>com.zaxxer</groupId>
      <artifactId>HikariCP</artifactId>
      <version>4.0.3</version> <!-- 替换为实际的最新版本 -->
    </dependency>

创建对象

public class HikaricpUtils {
    // 连接超时时间
    private static final long connectionTimeout = 3000;
    // 配置当前的数据库是否为只读状态
    private static final boolean readOnly = false;
    // 空闲连接超时时间
    private static final long idleTimeout = 6000;
    // 连接最大存活时间
    private static final long maxLifetime = 60000;
    // 最大连接数
    private static final int maxPoolSize = 20;
    // 最小空闲连接,默认值10,小于0或大于maximum-pool-size,都会重置为 maximum-pool-size。
    private static final int minIdle = 1; //

    public static HikariDataSource buildHikariDataSource(String driver, String url, String username, String password) {
        HikariConfig config = new HikariConfig();
        config.setPoolName(String.format("Hikari pool name: %s", url));
        config.setDriverClassName(driver);
        config.setJdbcUrl(url);
        config.setUsername(username);
        config.setPassword(password);

        config.setConnectionTimeout(connectionTimeout);
        config.setReadOnly(readOnly);
        config.setIdleTimeout(idleTimeout);
        config.setMaxLifetime(maxLifetime);
        config.setMaximumPoolSize(maxPoolSize);
        config.setMinimumIdle(minIdle);

        // 其它连接池配置参数可根据需要设置
        return new HikariDataSource(config);
    }
}

参数介绍:

  • connectionTimeout:连接超时时间:毫秒,小于250毫秒,否则被重置为默认值30秒
  • readOnly:配置当前的数据库是否为只读状态
  • idleTimeout:只有空闲连接数大于最大连接数且空闲时间超过该值,才会被释放。空闲连接超时时间,默认值600000(10分钟),大于等于max-lifetime且max-lifetime>0,会被重置为0;不等于0且小于10秒,会被重置为10秒。
  • maxLifetime:连接最大存活时间.不等于0且小于30秒,会被重置为默认值30分钟,设置应该比mysql设置的超时时间短
  • maxPoolSize:最大连接数
  • minIdle:最小空闲连接

注意:

  • idleTimeout大于等于max-lifetime且max-lifetime>0,会被重置为0;不等于0且小于10秒,会被重置为10秒。
  • 当设置MinimumIdle值时,若实际连接数未达到,HikariCP会尽力补充新的连接

创建HikariDataSource对象

调用HikaricpUtils为我们创建连接池对象。

class HikaricpUtilsTest {
    @Test
    void buildHikariDataSourceTest() throws SQLException {
        HikariDataSource hikariDataSource = HikaricpUtils.buildHikariDataSource("driver", "url", "username", "password");
        // 创建连接池对象之后,依然可以修改配置参数,再下一次创建conn时起作用
        hikariDataSource.setMaximumPoolSize(20); // 最大连接数为2
        hikariDataSource.setMinimumIdle(10);
        hikariDataSource.setConnectionTimeout(3000);
        hikariDataSource.setIdleTimeout(30000);
        hikariDataSource.setMaxLifetime(60000);

        Connection connection = hikariDataSource.getConnection();

        hikariDataSource.close(); // 关闭连接池
    }
}

创建Hikaricp连接,执行sql语句

HikariDataSource 为我们创建连接。

class HikaricpUtilsTest {
    @Test
    void getConnection() {
        HikariDataSource hikariDataSource = HikaricpUtils.buildHikariDataSource("driver", "url", "username", "password");
        try {
            Connection connection = hikariDataSource.getConnection();
            // 以下操作跟jdbc语法相同
            PreparedStatement preparedStatement = connection.prepareStatement("select * from t;");
            ResultSet resultSet = preparedStatement.executeQuery();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            hikariDataSource.close();
        }
    }
}

同一个连接池会复用连接

使用完的conn对象要调用conn.close,将conn释放回连接池,否则无法复用,还会导致超过最大连接数

    /**
     * 测试HikariDataSource连接池复用情况
     */
    @Test
    public void testReuseConnection() throws SQLException{
        HikariDataSource hikariDataSource = HikariUtils.buildHikariDataSource(url, user, password);
        hikariDataSource.setMaximumPoolSize(2); // TODO 设置最大连接数为2
        Connection conn1 = hikariDataSource.getConnection(); // TODO conn1
        Connection conn2 = hikariDataSource.getConnection(); // TODO conn2
        System.out.println(HikariUtils.executeQuery(conn1));
        conn1.close(); // TODO 要主动释放,否则会超过最大连接数,无法再打开新连接
        System.out.println(HikariUtils.executeQuery(conn2));
        System.out.println(HikariUtils.executeQuery(hikariDataSource.getConnection())); // TODO 此处会复用已释放回连接池的conn1
    }

    public static List<String> executeQuery(Connection conn) throws SQLException {
        List<String> connId = new ArrayList<>();
        try (PreparedStatement statement = conn.prepareStatement("select CONNECTION_ID()");
             ResultSet rs = statement.executeQuery()) {
            while (rs.next()) {
                connId.add(rs.getString(1));
            }
        }
        return connId;
    }

运行结果:

[274591]
[274592]
[274591]

连接池不同,创建的连接不能复用

    /**
     * 测试HikariDataSource连接池复用情况
     */
    @Test
    public void testReuseHikaricp() throws SQLException {
        HikariDataSource pool1 = HikariUtils.buildHikariDataSource(url, user, password);
        pool1.setMaximumPoolSize(2); // TODO 设置最大连接数为2
        Connection conn1 = pool1.getConnection(); // TODO conn1
        System.out.println(HikariUtils.executeQuery(conn1));
        conn1.close(); // TODO 要主动释放,否则会超过最大连接数,无法再打开新连接

        HikariDataSource pool2 = HikariUtils.buildHikariDataSource(url, user, password); // TODO 新的连接池pool2
        System.out.println(HikariUtils.executeQuery(HikariUtils.getConn(pool2))); // TODO 当然不会使用pool1中的conn

        System.out.println(HikariUtils.executeQuery(pool1.getConnection())); // TODO 此处会复用已释放回连接池pool1的conn1
    }

    public static List<String> executeQuery(Connection conn) throws SQLException {
        List<String> connId = new ArrayList<>();
        try (PreparedStatement statement = conn.prepareStatement("select CONNECTION_ID()");
             ResultSet rs = statement.executeQuery()) {
            while (rs.next()) {
                connId.add(rs.getString(1));
            }
        }
        return connId;
    }

运行结果:

[274593]
[274595]
[274593]

测试最小连接数MinimumIdle,以及空闲连接时间

当连接超出空闲连接时间,Hikaricp会将多余的连接释放,至少保留最小连接数。

    /**
     * 测试最小连接数MinimumIdle,以及空闲连接时间
     */
    @Test
    public void testMinimumIdle() throws SQLException, InterruptedException {
        HikariConfig config = new HikariConfig();
        config.setPoolName("HikariCP 连接池");
        config.setDriverClassName("com.mysql.cj.jdbc.Driver");
        config.setJdbcUrl(url);
        config.setUsername(user);
        config.setPassword(password);
        config.setMaximumPoolSize(2); // 设置最大连接数为2
        config.setMaxLifetime(60000); // 设置最大存活时间为60秒
        config.setMinimumIdle(0); // TODO 设置最小连接数为0
        config.setIdleTimeout(10000); // TODO 设置空闲连接超时时间为10s,超过10s的连接(已释放回连接池),若未使用将会被销毁
        HikariDataSource pool1 = new HikariDataSource(config);

        CompletableFuture.runAsync(() -> {
            try {
                Connection conn1 = pool1.getConnection();
                Connection conn2 = pool1.getConnection();
                System.out.println(HikariUtils.executeQuery(conn1));
                System.out.println(HikariUtils.executeQuery(conn2));
                conn1.close(); // 释放连接回连接池
                conn2.close(); // 释放连接回连接池
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        });

        Thread.sleep(40000); // TODO 将该值设置为小于最大存活时间MaxLifetime。避免因超过存活时间,而创建了新conn
        System.out.println(HikariUtils.executeQuery(pool1.getConnection())); // TODO 最小连接数为0,且此处超过了空闲连接时间,此处会创建新的连接connId
        pool1.close();
    }

运行结果:

[274649]
[274650]
[274651]

测试最大连接数

当连接超出最大连接数未关闭,Hikaricp则无法创建新连接。在超过ConnectionTimeout时间后还未获取到连接,导致获取连接失败。

    /**
     * 测试最大连接数
     */
    @Test
    public void testMaximumPoolSize() {
        HikariConfig config = new HikariConfig();
        config.setPoolName("HikariCP 连接池");
        config.setDriverClassName("com.mysql.cj.jdbc.Driver");
        config.setJdbcUrl(url);
        config.setUsername(user);
        config.setPassword(password);
        config.setConnectionTimeout(1000); // TODO 连接超时
        config.setMaximumPoolSize(2); // TODO 设置最大连接数为2
        config.setMaxLifetime(60000); // 设置最大存活时间为60秒
        config.setMinimumIdle(0); // 设置最小连接数为0
        config.setIdleTimeout(10000); // 设置空闲连接超时时间为10s,超过10s的连接(已释放回连接池),若未使用将会被销毁
        HikariDataSource pool1 = new HikariDataSource(config);

        try {
            Connection conn1 = pool1.getConnection();
            Connection conn2 = pool1.getConnection();
            Connection conn3 = pool1.getConnection(); // TODO 此处超过了最大连接数,在超过ConnectionTimeout时间后还未获取到连接,导致获取连接失败。
            System.out.println(HikariUtils.executeQuery(conn1));
            System.out.println(HikariUtils.executeQuery(conn2));
            conn1.close(); // 释放连接回连接池
            conn2.close(); // 释放连接回连接池
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }

        pool1.close();
    }

运行结果:

java.lang.RuntimeException: java.sql.SQLTransientConnectionException: HikariCP 连接池 - Connection is not available, request timed out after 1013ms.

测试最大存活时间

设置最小空闲连接为1,当超过最大存活时间后,原来的空闲连接id会释放,并创建一个新的空闲连接。

    /**
     * 测试最大存活时间
     */
    @Test
    public void testMaxLifetime() {
        HikariConfig config = new HikariConfig();
        config.setPoolName("HikariCP 连接池");
        config.setDriverClassName("com.mysql.cj.jdbc.Driver");
        config.setJdbcUrl(url);
        config.setUsername(user);
        config.setPassword(password);
        config.setConnectionTimeout(1000); // 连接超时
        config.setMaximumPoolSize(1); // TODO 设置最大连接数为1
        config.setMaxLifetime(35000); // TODO 设置最大存活时间为35秒
        config.setMinimumIdle(1); // TODO 设置最小连接数为1
        config.setIdleTimeout(35000); // // TODO 将空闲连接超时设置为与最大存活时间一致,保证连接不被收回
        HikariDataSource pool1 = new HikariDataSource(config);

        try {
            Connection conn1 = pool1.getConnection();
            System.out.println(HikariUtils.executeQuery(conn1));
            conn1.close(); // TODO 关闭,释放回连接池
            Connection conn2 = pool1.getConnection();
            System.out.println(HikariUtils.executeQuery(conn2)); // TODO 未超过最大存活时间,conn1和conn2的连接id是一样的
            conn2.close(); // TODO 关闭,释放回连接池

            Thread.sleep(40000); // TODO 超过最大存活时间
            Connection conn3 = pool1.getConnection();
            System.out.println(HikariUtils.executeQuery(conn3)); // TODO 超过了最大存活时间,conn3是一个新的连接id
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        pool1.close();
    }

运行结果:

[274680]
[274680]
[274681]

参考文档:

  1. Java 数据库连接池介绍(7)--HikariCP 介绍
  2. JDBC连接池
posted @   运行未来  阅读(208)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
点击右上角即可分享
微信分享提示