首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 數據庫知識 >  >> MySQL 資料庫 >> MySQL 綜合教學

JDBC 連接 MySQL 進階實戰(三):Apache DBCP 連接池整合與 EmpDAO 重構

     編輯:MySQL 綜合教學

JDBC 連接 MySQL 進階實戰(三):Apache DBCP 連接池整合與 EmpDAO 重構

針對上一節 DBUtility 存在的頻繁創建銷毀連接、無連接數上限、無復用機制三大痛點,本節引入 Apache Commons DBCP 2.x 實現生產級連接池,完成 ConnectionSource 重構與 EmpDAO 遷移。

一、為什麼需要連接池

痛點無連接池後果連接池解決
創建開銷大每請求握手、認證、分配會話,延遲 10-50ms預熱連接,借用即用,亞毫秒級
連接數失控併發衝擊擊穿 max_connections強制上限 maxActive,超時等待/拒絕
資源洩漏異常路徑未關閉,連接泄漏導致不可用借用/歸還機制,配合 removeAbandoned 強制回收

二、Maven 依賴

<dependencies>
    <dependency>
        <groupId>commons-dbcp</groupId>
        <artifactId>commons-dbcp</artifactId>
        <version>1.4</version> <!-- 或 2.x: org.apache.commons:commons-dbcp2 -->
    </dependency>
    <dependency>
        <groupId>commons-pool</groupId>
        <artifactId>commons-pool</artifactId>
        <version>1.6</version>
    </dependency>
    <!-- 部分版本需額外引入 -->
    <dependency>
        <groupId>commons-collections</groupId>
        <artifactId>commons-collections</artifactId>
        <version>3.2.2</version>
    </dependency>
</dependencies>

三、擴展配置文件:db.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/emp?useUnicode=true&characterEncoding=utf8
jdbc.user=wonderq
jdbc.password=root

# DBCP 連接池參數
dataSource.initialSize=10      # 初始化連接數
dataSource.minIdle=5           # 最小空閒連接
dataSource.maxIdle=20          # 最大空閒連接
dataSource.maxActive=50        # 最大活躍連接 (總上限)
dataSource.maxWait=1000        # 獲取連接超時時間 (毫秒)
dataSource.removeAbandoned=true      # 自動回收棄用連接
dataSource.removeAbandonedTimeout=60 # 棄用判定秒數
dataSource.logAbandoned=true       # 記錄棄用堆疊

四、ConnectionSource 核心實現

package dao;

import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import org.apache.commons.dbcp.BasicDataSource;

public class ConnectionSource {
    private static BasicDataSource dataSource;

    // 私有構造器,防實例化
    private ConnectionSource() {}

    // 雙重檢查鎖單例初始化
    private static synchronized void init() {
        if (dataSource != null) return;

        Properties props = new Properties();
        try (InputStream is = ConnectionSource.class.getClassLoader().getResourceAsStream("db.properties")) {
            if (is == null) throw new RuntimeException("db.properties not found");
            props.load(is);

            BasicDataSource ds = new BasicDataSource();
            ds.setDriverClassName(props.getProperty("jdbc.driver"));
            ds.setUrl(props.getProperty("jdbc.url"));
            ds.setUsername(props.getProperty("jdbc.user"));
            ds.setPassword(props.getProperty("jdbc.password"));

            // 讀取池參數 (提供默認值)
            ds.setInitialSize(parseInt(props, "dataSource.initialSize", 10));
            ds.setMinIdle(parseInt(props, "dataSource.minIdle", 5));
            ds.setMaxIdle(parseInt(props, "dataSource.maxIdle", 20));
            ds.setMaxWait(parseLong(props, "dataSource.maxWait", 1000));
            String maxActive = props.getProperty("dataSource.maxActive");
            if (maxActive != null && !"0".equals(maxActive.trim())) {
                ds.setMaxActive(Integer.parseInt(maxActive));
            }

            // 棄用連接回收
            ds.setRemoveAbandonedOnBorrow(true);
            ds.setRemoveAbandonedTimeout(60);
            ds.setLogAbandoned(true);

            dataSource = ds;
        } catch (IOException e) {
            throw new RuntimeException("Load db.properties failed", e);
        }
    }

    private static int parseInt(Properties p, String key, int def) {
        String v = p.getProperty(key);
        return (v != null) ? Integer.parseInt(v) : def;
    }
    private static long parseLong(Properties p, String key, long def) {
        String v = p.getProperty(key);
        return (v != null) ? Long.parseLong(v) : def;
    }

    // 獲取連接 (線程安全)
    public static synchronized Connection getConnection() throws SQLException {
        if (dataSource == null) init();
        return dataSource.getConnection();
    }

    // 關閉數據源 (應用關閉時調用)
    public static void shutdown() {
        if (dataSource != null) {
            try { dataSource.close(); } catch (SQLException e) { /* ignore */ }
            dataSource = null;
        }
    }
}

五、重構 EmpDAO:使用連接池

package dao;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class EmpDAO {
    public void findAll() {
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        try {
            conn = ConnectionSource.getConnection();  // 從池借用
            stmt = conn.createStatement();
            rs = stmt.executeQuery("SELECT empno, ename, sal, hiredate FROM emp");

            while (rs.next()) {
                System.out.printf("%d, %s, %.2f, %s%n",
                    rs.getInt("empno"),
                    rs.getString("ename"),
                    rs.getDouble("sal"),
                    rs.getDate("hiredate"));
            }
        } catch (SQLException e) {
            throw new RuntimeException("數據庫訪問異常", e);
        } finally {
            // 關閉順序:RS -> Stmt -> Conn (歸還池)
            if (rs != null) try { rs.close(); } catch (SQLException ignore) {}
            if (stmt != null) try { stmt.close(); } catch (SQLException ignore) {}
            if (conn != null) try { conn.close(); } catch (SQLException ignore) {}
        }
    }

    public static void main(String[] args) {
        new EmpDAO().findAll();
        ConnectionSource.shutdown();  // 關閉池 (演示用,實際由容器管理)
    }
}

六、運行驗證與效果

輸出結果與直連模式完全一致,但底層已實現連接復用。可通過 JMX/管理端點監控池狀態:

NumActive: 1
NumIdle: 9
MaxActive: 50

七、DBCP 核心參數調優指南

參數建議值調優依據
maxActiveCPU核數 × 2 ~ 4避免過多連接導致上下文切換、鎖競爭
initialSize / minIdle預期基礎併發量預熱減少首次借用延遲
maxWait1000-3000ms業務可容忍的最大等待時間
testOnBorrow / validationQuerytrue / SELECT 1防火牆/網絡斷開導致的失效連接
removeAbandonedtrue防業務代碼忘記 close 洩漏
timeBetweenEvictionRunsMillis30000-60000空閒連接驅逐檢查間隔

八、遷移建議:DBCP 1.x → 2.x / HikariCP

  • DBCP 1.x:已停止維護,存在已知併發性能瓶頸。
  • DBCP 2.x:支援 JDBC 4.1+、更好併發、模塊化。
  • HikariCP:現代首選,極致性能、零依賴、約 130KB,Spring Boot 2.x 默認連接池。
# HikariCP 快速上手
HikariConfig config = new HikariConfig();
config.setJdbcUrl(url);
config.setUsername(user);
config.setPassword(pass);
config.setMaximumPoolSize(20);
HikariDataSource ds = new HikariDataSource(config);
Connection conn = ds.getConnection();

總結

引入 Apache DBCP 連接池,通過 ConnectionSource 封裝池生命周期,EmpDAO 無感遷移,徹底解決「頻繁創建、數量失控、洩漏無感」三大痛點。生產環境強烈建議升級 HikariCP,享受更高吞吐、更低延遲、更簡潔配置。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved