1. 来源
有时候我们在编写一些简单的需要操作数据库的工具时,可能就用不着复杂的spring、mybatis这样的框架,需要尽量的简单化。从下面的测试数据可以看出,创建数据库连接平均耗时1.8秒,释放连接平均耗时0.2秒,如果不使用连接池,那么每次数据库操作都会多花至少2秒的时间来创建和释放连接。当使用了连接池后,只需要在启动和结束的时候花点时间初始化和释放连接,在中途真正操作数据库时会节省大量的时间,提升系统的性能。
- 连接耗时测试
==========================开始初始化【源库】连接池===================================
[1/10]加载数据库驱动开始
[1/10]加载数据库驱动完成
[1/10]获取数据库连接开始
[1/10]获取数据库连接完成,耗时:2658
[2/10]加载数据库驱动开始
[2/10]加载数据库驱动完成
[2/10]获取数据库连接开始
[2/10]获取数据库连接完成,耗时:1389
[3/10]加载数据库驱动开始
[3/10]加载数据库驱动完成
[3/10]获取数据库连接开始
[3/10]获取数据库连接完成,耗时:1401
[4/10]加载数据库驱动开始
[4/10]加载数据库驱动完成
[4/10]获取数据库连接开始
[4/10]获取数据库连接完成,耗时:1297
[5/10]加载数据库驱动开始
[5/10]加载数据库驱动完成
[5/10]获取数据库连接开始
[5/10]获取数据库连接完成,耗时:2598
......中间过程省略......
正在等待关闭连接池,释放数据库连接...
成功释放【源库】第1/10个连接,单步耗时:198ms,总耗时:198ms
成功释放【源库】第2/10个连接,单步耗时:175ms,总耗时:373ms
成功释放【源库】第3/10个连接,单步耗时:235ms,总耗时:608ms
成功释放【源库】第4/10个连接,单步耗时:187ms,总耗时:795ms
成功释放【源库】第5/10个连接,单步耗时:200ms,总耗时:995ms
3. 连接池源码
package com.open.data.migrate.source.to.target.pool;
import com.open.data.migrate.source.to.target.config.DbConfig;
import com.open.data.migrate.source.to.target.util.DbUtils;
import com.open.data.migrate.source.to.target.util.LogUtils;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.concurrent.ArrayBlockingQueue;
/**
* @author 2810010108@qq.com
* @project open-component-migrate-to-mysql
* @package com.open.component.convert.column.encoding.pool
* @date 2020/9/10 15:45
* @description 简单连接池,源库和目标库分别创建各自的连接池,连接池使用阻塞队列实现
**/
public final class ConnectionPool {
/**
* 源库连接池,默认容量为100,可以通过其他方式支持可配
*/
private static ArrayBlockingQueue<Connection> SOURCE_CONNECTION_POOL = new ArrayBlockingQueue<Connection>(100);
/**
* 目标库连接池,默认容量为100,可以通过其他方式支持可配
*/
private static ArrayBlockingQueue<Connection> TARGET_CONNECTION_POOL = new ArrayBlockingQueue<Connection>(100);
static {
// 类加载时初始化数据库连接池
initConnectionPool();
}
/**
* 初始化数据库连接池方法,通过DbUtils从配置文件读取数据库连接信息,创建N个数据库连接存入队列,N为配置的数据库连接数
*/
private static void initConnectionPool() {
DbConfig sourceDbConfig = DbUtils.getSourceDbConfig();
LogUtils.printInfoLog("==========================开始初始化【源库】连接池===================================");
for (int i = 0; i < sourceDbConfig.getConnections(); i++) {
Connection connection = initConnection(sourceDbConfig, i + 1);
SOURCE_CONNECTION_POOL.add(connection);
}
LogUtils.printInfoLog("==========================结束初始化【源库】连接池===================================");
LogUtils.printInfoLog("==========================开始初始化【目标库】连接池===================================");
DbConfig targetDbConfig = DbUtils.getTargetDbConfig();
for (int i = 0; i < targetDbConfig.getConnections(); i++) {
Connection connection = initConnection(targetDbConfig, i + 1);
TARGET_CONNECTION_POOL.add(connection);
}
LogUtils.printInfoLog("==========================结束初始化【目标库】连接池===================================");
}
/**
* 从源库连接池中阻塞的取得一个连接,队列的take()方法为阻塞式出队
*
* @return 源库连接
*/
public static Connection borrowSource() {
try {
return SOURCE_CONNECTION_POOL.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
/**
* 数据库操作完毕,阻塞的归还源库连接到连接池,队列的put操作为阻塞式入队
*
* @param connection 要归还的源库连接
*/
public static void returnSource(Connection connection) {
try {
SOURCE_CONNECTION_POOL.put(connection);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 从目标库连接池中阻塞的取得一个连接,队列的take()方法为阻塞式出队
*
* @return 源库连接
*/
public static Connection borrowTarget() {
try {
return TARGET_CONNECTION_POOL.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
/**
* 数据库操作完毕,阻塞的归还目标库连接到连接池,队列的put操作为阻塞式入队
*
* @param connection 要归还的源库连接
*/
public static void returnTarget(Connection connection) {
try {
TARGET_CONNECTION_POOL.put(connection);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* @param config 数据库连接信息对象
* @param number 序号,用于打印正在初始化的数据库连接序号
* @return 创建完成的数据库连接
*/
private static Connection initConnection(DbConfig config, int number) {
Connection connection = null;
try {
//加载数据驱动
LogUtils.printInfoLog(String.format("[%s/%s]加载数据库驱动开始", number, config.getConnections()));
Class.forName(config.getDriver());
LogUtils.printInfoLog(String.format("[%s/%s]加载数据库驱动完成", number, config.getConnections()));
// 连接数据库
LogUtils.printInfoLog(String.format("[%s/%s]获取数据库连接开始", number, config.getConnections()));
connection = DriverManager.getConnection(config.getUrl(), config.getUsername(), config.getPassword());
LogUtils.printInfoLog(String.format("[%s/%s]获取数据库连接完成", number, config.getConnections()));
} catch (ClassNotFoundException e) {
e.printStackTrace();
LogUtils.printErrorLog(String.format("[%s/%s]加载数据库驱动失败", number, config.getConnections()));
} catch (Exception e) {
e.printStackTrace();
LogUtils.printErrorLog(String.format("[%s/%s]连接数据库失败", number, config.getConnections()));
}
return connection;
}
/**
* 关闭所有的源库和目标库的连接
*/
public static void closeAllConnections() {
close(SOURCE_CONNECTION_POOL, "源库");
close(TARGET_CONNECTION_POOL, "目标库");
}
/**
* 释放传入连接池中所有的连接,避免数据库连接被恶意占用
*
* @param pool 要关闭的连接池
* @param type 库类别标识,用于打印日志
*/
public static void close(ArrayBlockingQueue<Connection> pool, String type) {
int size = pool.size();
int counter = 0;
long start = System.currentTimeMillis();
while (true) {
long startInner = System.currentTimeMillis();
Connection connection = pool.poll();
if (connection == null) {
break;
}
try {
connection.close();
LogUtils.printInfoLog(String.format("成功释放【%s】第%s/%s个连接,单步耗时:%sms,总耗时:%sms", type, ++counter, size, System.currentTimeMillis() - startInner, System.currentTimeMillis() - start));
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
3. 出处
该连接池来自于数据库迁移工具,该工具无任何的框架,纯JDK实现,jar运行。可以实现的数据流向有:
- mysql->mysql
- mysql->oracle
- oracle->oracle
-
oracle->mysql
还可以通过引入驱动包,扩展SourceDbEnums和TargetDbEnums枚举类,可以实现其他数据库支持
项目地址:
https://gitee.com/mengbaoyuxuan/open-data-migrate-source-to-target.git
交流QQ群:566654343
版权声明:本文为musuny原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。