Database Connection Pool 資料庫連線池-01-概覽及簡單手寫實現

老马啸西风發表於2024-03-14

擴充閱讀

第一節 從零開始手寫 mybatis(一)MVP 版本

第二節 從零開始手寫 mybatis(二)mybatis interceptor 外掛機制詳解

第三節 從零開始手寫 mybatis(三)jdbc pool 從零實現資料庫連線池

第四節 從零開始手寫 mybatis(四)- mybatis 事務管理機制詳解

連線池的作用

資源重用

由於資料庫連線得到重用,避免了頻繁建立、釋放連線引起的大量效能開銷。在減少系統消耗的基礎上,
另一方面也增進了系統執行環境的平穩性(減少記憶體碎片以及資料庫臨時程序/執行緒的數量)。

更快的系統響應速度

資料庫連線池在初始化過程中,往往已經建立了若干資料庫連線置於池中備用。此時連線的初始化工作均已完成。
對於業務請求處理而言,直接利用現有可用連線,避免了資料庫連線初始化和釋放過程的時間開銷,從而縮減了系統整體響應時間。

新的資源分配手段

對於多應用共享同一資料庫的系統而言,可在應用層透過資料庫連線的配置,使用資料庫連線池技術。
設定某一應用最大可用資料庫連線數,避免某一應用獨佔所有資料庫資源。

統一的連線管理,避免資料庫連線洩漏

在較為完備的資料庫連線池實現中,可根據預先設定的連線佔用超時時間,強制收回被超時佔用的連線。
從而避免了常規資料庫連線操作中可能出現的資源洩漏(當程式存在缺陷時,申請的連線忘記關閉,這時候,就存在連線洩漏了)。

中介軟體

常見實現對比

參考網上資料Druid > TomcatJDBC > DBCP > C3P0,BoneCP 的效能方面沒有深入比較,應該和 Tomcat Jdbc 差不多。

對於小型的系統,併發壓力不大時,選擇哪一種資料庫連線池差別不會很大,主要考慮的應該是連線池的穩定性。

當併發量較高時,一般不會選擇使用 DBCP 和C3P0,選 Druid 是較好的。

手動實現

自己實現一個簡化版,便於理解原理。

  • 連線池介面
public interface IPool {
    /**
     * 獲取新的資料庫連結
     * @return 資料庫連結
     */
    PoolConnection getPoolConnection();
}

其中 PoolConnection 如下:

public class PoolConnection {
    /**
     * 是否繁忙
     */
    private volatile boolean isBusy;

    /**
     * 資料庫連結資訊
     */
    private Connection connection;
}
  • 核心實現
public class PoolImpl implements IPool {

    /**
     * 資料庫驅動
     */
    private final String jdbcDriver;

    /**
     * 資料庫連線
     */
    private final String jdbcUrl;

    /**
     * 資料庫使用者名稱
     */
    private final String username;

    /**
     * 資料庫密碼
     */
    private final String passowrd;

    /**
     * 連線池大小
     */
    private final int size;

    /**
     * 資料庫連線池列表
     */
    private List<PoolConnection> poolConnections = new ArrayList<>();

    public PoolImpl(String jdbcDriver, String jdbcUrl, String username, String passowrd, int size) {
        this.jdbcDriver = jdbcDriver;
        this.jdbcUrl = jdbcUrl;
        this.username = username;
        this.passowrd = passowrd;
        this.size = size;

        init();
    }

    private void init() {
        try {
            //1. 註冊資料庫連線資訊
            Driver sqlDriver = (Driver) Class.forName(jdbcDriver).newInstance();
            DriverManager.registerDriver(sqlDriver);

            //2. 初始化連線池
            initConnectionPool();
        } catch (InstantiationException | IllegalAccessException | SQLException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * 初始化連結
     * @throws SQLException sql 異常
     */
    private void initConnectionPool() throws SQLException {
        for(int i = 0; i < size; i++) {
            Connection connection = DriverManager.getConnection(jdbcUrl, username, passowrd);
            PoolConnection poolConnection = new PoolConnection(false, connection);
            poolConnections.add(poolConnection);
        }
    }

    @Override
    public PoolConnection getPoolConnection() {
        if(poolConnections.size() <= 0) {
            return null;
        }

        PoolConnection poolConnection = getRealConnection();
        while (poolConnection == null) {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            poolConnection = getRealConnection();
        }

        return poolConnection;
    }

    /**
     * 獲取資料庫連結物件
     * @return 資料庫連結物件
     */
    private synchronized PoolConnection getRealConnection() {
        for(PoolConnection poolConnection : poolConnections) {
            // 尋找不處於繁忙狀態的連線
            if(!poolConnection.isBusy()) {
                Connection connection = poolConnection.getConnection();

                // 測試當前連線是否有效
                try {
                    if(!connection.isValid(5000)) {
                        Connection validConnection = DriverManager.getConnection(jdbcUrl, username, passowrd);
                        poolConnection.setConnection(validConnection);
                    }
                } catch (SQLException e) {
                    e.printStackTrace();
                }

                // 設定為繁忙
                poolConnection.setBusy(true);
                return poolConnection;
            }
        }

        return null;
    }
}
  • 執行緒池管理類

使用單例

public class PoolManager {

    /**
     * 連線池持有類
     */
    private static class PoolHolder {
        private static String url = "";
        private static String driver = "";
        private static String username = "";
        private static String password = "";
        private static int size = 10;

        private static IPool poolImpl = new PoolImpl(driver, url, username, password, size);
    }

    /**
     * 內部類單利模式產生使用物件
     * @return 單例
     */
    public static IPool getInstance() {
        return PoolHolder.poolImpl;
    }
}

相關文章