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]
参考文档:
本文来自博客园,作者:运行未来,转载请注明原文链接:https://www.cnblogs.com/running-future/p/18265819
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析