用java实现JDBC数据库连接池
用 Java 实现 JDBC 数据库连接池
本文旨在解决 Servlet 访问数据库时的稳定性问题。通过研究并实现一个适合自身需求的数据库连接池(Database Connection Pool),我们可以更好地控制程序与数据库之间建立的连接。使用连接池不仅能减小数据库访问压力,便于统一管理连接,还能显著提高连接的利用率和工作性能。
在设计数据库连接池时,个人认为应重点关注以下几点:
- 控制连接池大小:能够限制最大连接数,防止资源耗尽。
- 统一获取接口:提供一个统一的接口用于获取数据库连接。
- 连接回收机制:使用后的连接需要有接口能够接收并处理,使其回归池化状态。
- 自我维护能力:连接池应具备自我维护能力,例如暂时提高连接池大小以应对连接请求高峰,或定期处理多余及无效的连接。
数据结构设计
首先,我们确定连接池的基础数据结构。我们需要维护空闲连接列表、已使用连接集合以及数据库配置信息。
public class SimpleConnectionPool {
private static LinkedList<Connection> m_notUsedConnection = new LinkedList<>();
private static HashSet<Connection> m_usedConnection = new HashSet<>();
private static String m_url = "";
private static String m_user = "";
private static String m_password = "";
private static int m_maxConnect = 3;
static final boolean DEBUG = false;
static private long m_lastClearClosedConnection = System.currentTimeMillis();
public static long CHECK_CLOSED_CONNECTION_TIME = 5000; // 5 秒
}核心逻辑实现
1. 清除多余及无效连接
我们需要定期清除连接池中多余的连接。每隔一段时间对池中的所有连接进行检查:第一轮循环判断连接是否已关闭,若已关闭则直接移除;第二轮循环则根据目前规定的最大数量裁撤空闲连接。
private static void clearClosedConnection() {
long time = System.currentTimeMillis();
// 时间不合理,没有必要检查
if (time < m_lastClearClosedConnection) {
time = m_lastClearClosedConnection;
return;
}
// 时间太短,没有必要检查
if (time - m_lastClearClosedConnection < CHECK_CLOSED_CONNECTION_TIME) {
return;
}
m_lastClearClosedConnection = time;
// 开始检查没有使用的 Connection
Iterator<Connection> iterator = m_notUsedConnection.iterator();
while (iterator.hasNext()) {
Connection con = iterator.next();
try {
if (con.isClosed()) {
iterator.remove();
}
} catch (SQLException e) {
iterator.remove();
if (DEBUG) {
System.out.println("问题连接已断开");
}
}
}
// 清除多余的 Connection
int decrease = getDecreasingConnectionCount();
while (decrease > 0 && m_notUsedConnection.size() > 0) {
Connection con = m_notUsedConnection.removeFirst();
try {
con.close();
} catch (SQLException e) {
// 忽略关闭异常
}
decrease--;
}
}2. 获取新连接
申请新连接时,首先调用清理器清除多余和无法使用的连接。随后在空闲连接中寻找可用连接,若有符合的则直接分配。若未找到,则需要建立新的连接。建立新连接后,将其中一个分配出去,剩下的加入到空闲连接中等待分配。
public static synchronized Connection getConnection() {
// 清除多余的连接
clearClosedConnection();
// 输出当前总连接数
if (DEBUG)
System.out.println("当前总连接数:" + getConnectionCount());
// 寻找空闲的连接
while (m_notUsedConnection.size() > 0) {
try {
Connection con = m_notUsedConnection.removeFirst();
if (con.isClosed()) {
continue;
}
m_usedConnection.add(con);
if (DEBUG) {
// System.out.println("连接初始化成功");
}
return con;
} catch (SQLException e) {
// 忽略异常,继续尝试
}
}
// 没有找到,建立一些新的连接以供使用
int newCount = getIncreasingConnectionCount();
LinkedList<Connection> list = new LinkedList<>();
Connection con = null;
for (int i = 0; i < newCount; i++) {
con = getNewConnection();
if (con != null) {
list.add(con);
}
}
// 没有成功建立连接,访问失败
if (list.size() == 0)
return null;
// 成功建立连接,使用的加入 used 队列,剩下的加入 notUsed 队列
con = list.removeFirst();
m_usedConnection.add(con);
m_notUsedConnection.addAll(list);
list.clear();
return con;
}3. 归还连接
根据设计原则,我们需要一个接口用于交还连接。逻辑很简单:将占用中的连接从已使用集合移出,放回到空闲连接列表中。
static synchronized void pushConnectionBackToPool(Connection con) {
boolean exist = m_usedConnection.remove(con);
if (exist) {
m_notUsedConnection.addLast(con);
}
}完整代码实现
以上是连接池的核心部分,整合后的完整代码如下。需要注意的关键点已在上述章节中说明。
package cn.com.css.cas.jdbc;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
/**
* JDBC 数据库连接池
*
* @author Woud
*/
public class SimpleConnectionPool {
private static LinkedList<Connection> m_notUsedConnection = new LinkedList<>();
private static HashSet<Connection> m_usedConnection = new HashSet<>();
private static String m_url = "";
private static String m_user = "";
private static String m_password = "";
private static int m_maxConnect = 3;
static final boolean DEBUG = false;
static private long m_lastClearClosedConnection = System.currentTimeMillis();
public static long CHECK_CLOSED_CONNECTION_TIME = 5000; // 5 秒
static {
try {
initDriver();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public SimpleConnectionPool(String url, String user, String password) {
m_url = url;
m_user = user;
m_password = password;
}
private static void initDriver() throws InstantiationException,
IllegalAccessException, ClassNotFoundException {
Driver driver = null;
// 读取 MySql 的 Driver
driver = (Driver) Class.forName("com.mysql.jdbc.Driver").newInstance();
installDriver(driver);
/*
* // 读取 postgresql 的 driver
* driver = (Driver) Class.forName("org.postgresql.Driver").newInstance();
* installDriver(driver);
*/
}
public static void installDriver(Driver driver) {
try {
DriverManager.registerDriver(driver);
} catch (SQLException e) {
e.printStackTrace();
}
}
public static synchronized Connection getConnection() {
// 清除多余的连接
clearClosedConnection();
// 输出当前总连接数
if (DEBUG)
System.out.println("当前总连接数:" + getConnectionCount());
// 寻找空闲的连接
while (m_notUsedConnection.size() > 0) {
try {
Connection con = m_notUsedConnection.removeFirst();
if (con.isClosed()) {
continue;
}
m_usedConnection.add(con);
if (DEBUG) {
// System.out.println("连接初始化成功");
}
return con;
} catch (SQLException e) {
// 忽略异常
}
}
// 没有找到,建立一些新的连接以供使用
int newCount = getIncreasingConnectionCount();
LinkedList<Connection> list = new LinkedList<>();
Connection con = null;
for (int i = 0; i < newCount; i++) {
con = getNewConnection();
if (con != null) {
list.add(con);
}
}
// 没有成功建立连接,访问失败
if (list.size() == 0)
return null;
// 成功建立连接,使用的加入 used 队列,剩下的加入 notUsed 队列
con = list.removeFirst();
m_usedConnection.add(con);
m_notUsedConnection.addAll(list);
list.clear();
return con;
}
public static Connection getNewConnection() {
try {
Connection con = DriverManager.getConnection(m_url, m_user, m_password);
return con;
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
static synchronized void pushConnectionBackToPool(Connection con) {
boolean exist = m_usedConnection.remove(con);
if (exist) {
m_notUsedConnection.addLast(con);
}
}
public static int close() {
int count = 0;
Iterator<Connection> iterator = m_notUsedConnection.iterator();
while (iterator.hasNext()) {
try {
iterator.next().close();
count++;
} catch (SQLException e) {
e.printStackTrace();
}
}
m_notUsedConnection.clear();
iterator = m_usedConnection.iterator();
while (iterator.hasNext()) {
try {
iterator.next().close();
} catch (SQLException e) {
e.printStackTrace();
}
}
m_usedConnection.clear();
return count;
}
private static void clearClosedConnection() {
long time = System.currentTimeMillis();
// 时间不合理,没有必要检查
if (time < m_lastClearClosedConnection) {
time = m_lastClearClosedConnection;
return;
}
// 时间太短,没有必要检查
if (time - m_lastClearClosedConnection < CHECK_CLOSED_CONNECTION_TIME) {
return;
}
m_lastClearClosedConnection = time;
// 开始检查没有使用的 Connection
Iterator<Connection> iterator = m_notUsedConnection.iterator();
while (iterator.hasNext()) {
Connection con = iterator.next();
try {
if (con.isClosed()) {
iterator.remove();
}
} catch (SQLException e) {
iterator.remove();
if (DEBUG) {
System.out.println("问题连接已断开");
}
}
}
// 清除多余的 Connection
int decrease = getDecreasingConnectionCount();
while (decrease > 0 && m_notUsedConnection.size() > 0) {
Connection con = m_notUsedConnection.removeFirst();
try {
con.close();
} catch (SQLException e) {
// 忽略异常
}
decrease--;
}
}
public static int getIncreasingConnectionCount() {
int count = 1;
count = getConnectionCount() / 4;
if (count < 1)
count = 1;
return count;
}
public static int getDecreasingConnectionCount() {
int count = 0;
if (getConnectionCount() > m_maxConnect) {
count = getConnectionCount() - m_maxConnect;
}
return count;
}
public static synchronized int getNotUsedConnectionCount() {
return m_notUsedConnection.size();
}
public static synchronized int getUsedConnectionCount() {
return m_usedConnection.size();
}
public static synchronized int getConnectionCount() {
return m_notUsedConnection.size() + m_usedConnection.size();
}
}使用建议
完成连接池的开发后,我们可以通过 Singleton(单例)模式 创建一个连接池管理器。对接口进行简单封装后,管理器调用连接池的 getConnection 接口获得连接,与数据库建立连接并执行 SQL,运行结束后交还连接并把结果反馈回来即可。
说明:
- 驱动版本:代码中使用的
com.mysql.jdbc.Driver属于较旧版本的 MySQL Connector/J(5.x 及以前)。在新版本(8.x+)中,驱动类名通常变更为com.mysql.cj.jdbc.Driver,请根据实际依赖调整。 - Java 版本:原文代码风格较早(如未使用泛型),润色后的代码已补充泛型支持以符合现代 Java 规范,但核心逻辑保持不变。
- 线程安全:该实现通过
synchronized方法保证基本的线程安全,但在高并发场景下,建议考虑使用java.util.concurrent包下的成熟连接池实现(如 HikariCP、Druid 等)。
版权声明:本文为原创文章,版权归 戴老师的博客 所有,转载请联系博主获得授权。
本文地址:https://1diff.fun/archives/yong-java-shi-xian-jdbc-shu-ju-ku-lian-jie-chi.html
如果对本文有什么问题或疑问都可以在评论区留言,我看到后会尽量解答。