用 Java 实现 JDBC 数据库连接池

本文旨在解决 Servlet 访问数据库时的稳定性问题。通过研究并实现一个适合自身需求的数据库连接池(Database Connection Pool),我们可以更好地控制程序与数据库之间建立的连接。使用连接池不仅能减小数据库访问压力,便于统一管理连接,还能显著提高连接的利用率和工作性能。

在设计数据库连接池时,个人认为应重点关注以下几点:

  1. 控制连接池大小:能够限制最大连接数,防止资源耗尽。
  2. 统一获取接口:提供一个统一的接口用于获取数据库连接。
  3. 连接回收机制:使用后的连接需要有接口能够接收并处理,使其回归池化状态。
  4. 自我维护能力:连接池应具备自我维护能力,例如暂时提高连接池大小以应对连接请求高峰,或定期处理多余及无效的连接。

数据结构设计

首先,我们确定连接池的基础数据结构。我们需要维护空闲连接列表、已使用连接集合以及数据库配置信息。

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,运行结束后交还连接并把结果反馈回来即可。


说明

  1. 驱动版本:代码中使用的 com.mysql.jdbc.Driver 属于较旧版本的 MySQL Connector/J(5.x 及以前)。在新版本(8.x+)中,驱动类名通常变更为 com.mysql.cj.jdbc.Driver,请根据实际依赖调整。
  2. Java 版本:原文代码风格较早(如未使用泛型),润色后的代码已补充泛型支持以符合现代 Java 规范,但核心逻辑保持不变。
  3. 线程安全:该实现通过 synchronized 方法保证基本的线程安全,但在高并发场景下,建议考虑使用 java.util.concurrent 包下的成熟连接池实现(如 HikariCP、Druid 等)。