走進數據庫連接池

1. 前言

         “池”(Pool)技術在一定程度上可以明顯優化服務器應用程序的性能,提高程序執行效率和降低系統資源開銷。此“池”是一種廣義上的池,如:數據庫連接池、線程池、內存池。此處只涉及到數據庫連接池

2. 數據庫連接池

2.1 什麼是數據庫連接池?

        那麼,什麼是數據庫連接池呢?數據庫連接池就是一個創建和管理數據庫連接的緩衝池技術,這些連接隨時會被需要它們的線程調用。

2.2 java中常用的數據庫連接池

        在java語言中,一些常用的數據庫連接池有:C3P0、DBCP、Proxool以及阿里巴巴的Druid。
        前三個數據庫連接池的介紹及使用:Java中三種流行的數據連接池

2.3 數據庫連接池的設計思想

        數據庫連接池的設計思想:爲數據庫連接建立一個“緩衝池”,預先在池中放入一定數量的數據庫連接管道,需要時,從池子中取出管道進行使用,操作完畢後,在將管道放入池子中,從而避免了頻繁的向數據庫申請資源,釋放資源帶來的性能損耗。

2.4 爲什麼要使用數據庫連接池?

        在我們連接數據庫的時候如果不使用數據連接池,每一次訪問數據庫都去創建一個連接,如果用戶量比較大的話,這樣會很消耗時間和項目的性能。而使用了數據庫連接池後,就不需要頻繁地創建連接以及釋放資源等,這樣,就可以提升服務器的性能。

        查看此文----爲什麼數據庫連接很消耗資源,可以進一步瞭解數據庫連接爲什麼很消耗資源

2.5 手寫一個迷你版數據庫連接池

工程結構圖:
在這裏插入圖片描述
         數據庫連接池工作流程: 首先是配置文件包含(數據庫連接地址,數據庫用戶名,數據庫密碼,數據庫驅動,連接池數量,開始初始化數量,自動增長數量);然後讀取配置文件,初始化線程池,獲取連接池中連接。使用時,更改其狀態,如連接池中連接不夠用則自動創建對應配置文件中數量的連接,並判斷是否超過連接池最大數量,當連接使用完後,重新將連接放入池中供其他線程使用,從而達到可複用的效果。

1.配置文件jdbc.properties
配置與數據庫連接有關的信息

jdbcDriver=com.mysql.jdbc.Driver
jdbcUrl=jdbc:mysql://localhost:3306/test02?useUnicode=true&characterEncoding=UTF-8
username=root
password=root
initCounts=10
maxCount=50
incrementCount=2

2.提供一個封裝類:數據庫連接管道MyPooledConnection
        此類包含一個數據庫連接、一個是否使用的標記、query()方法(提供基本的SQL查詢功能),以及一個close()方法(用來將該連接管道置爲可用狀態,從而達到數據庫連接管道的可複用,減少持續創建新連接管道的資源消耗)

@Data
public class MyPooledConnection {
    /**
     * 數據庫連接
     */
    private Connection conn;

    /**
     * 標記該連接管道是否可用
     */
    private boolean isBusy;

    public MyPooledConnection(Connection conn, boolean isBusy) {
        this.conn = conn;
        this.isBusy = isBusy;
    }

    public void close() {
        this.isBusy = false;
    }

    public ResultSet query(String sql) {
        Statement stmt = null;
        ResultSet rs = null;

        try {
            stmt = conn.createStatement();
            rs = stmt.executeQuery(sql);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return rs;
    }
}

        所謂數據庫連接管道,就是對JDBC Connection進行封裝而已,但是需要注意isBusy的這個標示。對管道的關閉,實際上只是標識的改變而已!

3. 數據庫連接管道接口IMyPool
對數據庫連接管道提供一個基本管理API接口:

  • 獲取數據庫連接管道
  • 創建數據庫連接管道
public interface IMyPool {
    MyPooledConnection getMyPooledConnection();
    void createMyPooledConnection(int count);
}

4. 數據庫連接池MyDefaultPool
        首先加載對應配置文件中信息,初始化數據庫連接池,然後用synchronized來實現多線程情況下線程安全的獲取可用的連接管道

MyDefaultPool的屬性:

public class MyDefaultPool implements IMyPool {

    // 基於多線程的考慮,這裏使用了Vector。
    private static Vector<MyPooledConnection> connections = new Vector<>();
    private static String jdbcDriver;
    private static String jdbcUrl;
    private static String username;
    private static String password;
    private static Integer initCounts;
    private static Integer maxCounts;
    private static Integer incrementCounts;

        需要注意到是,MyDefaultPool持有一個管道集合,基於多線程的考慮,這裏使用了Vector。

MyDefaultPool初始化

public MyDefaultPool() {
    init();

    try {
        Class.forName(jdbcDriver);
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }

    // 初始化數據庫連接池中的連接管道
    createMyPooledConnection(initCounts);
}

private void init() {
    // 讀取對應的配置文件,加載入properties中,並設置到對應的參數中
    InputStream in = MyDefaultPool.class.getClassLoader().getResourceAsStream("jdbc.properties");
    Properties properties = new Properties();
    try {
        properties.load(in);
    } catch (IOException e) {
        e.printStackTrace();
    }

    jdbcDriver = properties.getProperty("jdbcDriver");
    jdbcUrl = properties.getProperty("jdbcUrl");
    username = properties.getProperty("username");
    password = properties.getProperty("password");
    initCounts = Integer.valueOf(properties.getProperty("initCounts"));
    maxCounts = Integer.valueOf(properties.getProperty("maxCounts"));
    incrementCounts = Integer.valueOf(properties.getProperty("incrementCounts"));
}

        數據庫連接池需要根據外部配置文件完成數據庫驅動加載以及初始化管道的建立。

createMyPooledConnection接口實現

@Override
public void createMyPooledConnection(int count) {
    if (connections.size() > maxCounts ||
            connections.size() + count > maxCounts) {
        throw new RuntimeException("連接池已滿");
    }

    for (int i = 0; i < count; i++) {
        try {
            Connection connection = DriverManager.getConnection(jdbcUrl, username, password);
            connections.add(new MyPooledConnection(connection, false));
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

        數據庫連接池在創建管道時,應該去看一下是否達到上限,如果沒有,則可以創建。不僅僅要創建出來,還要標示每一個管道的isBusy標誌。

getMyPooledConnection接口實現

@Override
public MyPooledConnection getMyPooledConnection() {
    if (connections.size() < 1) {
        throw new RuntimeException("連接池初始化錯誤!");
    }

    MyPooledConnection myPooledConnection = null;
    try {
        myPooledConnection = getRealConnection();
        while (null == myPooledConnection) {
            createMyPooledConnection(incrementCounts);
            myPooledConnection = getRealConnection();
        }
    } catch (SQLException e) {
        e.printStackTrace();
    }

    return myPooledConnection;
}

        這裏需要注意的是:如果得不到操作管道,需要去創建管道!

getRealConnection

private synchronized MyPooledConnection getRealConnection() throws SQLException {
    for (MyPooledConnection myPooledConnection : connections) {
        if (!myPooledConnection.isBusy()) {
            // Connection是有超時機制的,一定不能獲取到超時的Connection
            if (myPooledConnection.getConn().isValid(3000)) {
                myPooledConnection.setBusy(true);
            } else {
                // 如果連接超時,則新建一個連接管道
                Connection conn = DriverManager.getConnection(jdbcUrl, username, password);
                myPooledConnection.setConn(conn);
                myPooledConnection.setBusy(true);
            }
            return myPooledConnection;
        }
    }
    return null;
}

第一,這裏使用了synchronized,就是爲了避免多線程下產生問題。
第二,要知道Connection是有超時機制的,如果我們得到的管道的Connection已經超時了怎麼辦呢?此處是重新新建了一個連接管道。但有2個疑問(主要針對的是這個連接管道被數據庫連接池管理):1)、新建連接時,並沒有判斷數據庫連接池是否達到最大連接數;2)、新建的連接管道並沒有添加到數據庫連接池中去
第三,得到管道後,一定注意isBusy的設置。

5. 數據庫連接池工廠MyPoolFactory

public class MyPoolFactory {

    public static class CreatePool {
        public static IMyPool myPool = new MyDefaultPool();
    }

    public static IMyPool getInstance() {
        return CreatePool.myPool;
    }
}

6. 測試類Test

public class Test {
    public static IMyPool myPool = MyPoolFactory.getInstance();

    public static void main(String[] args) {
        for (int i = 0; i < 50; i++) {
            MyPooledConnection myPooledConnection = myPool.getMyPooledConnection();
            String sql = "select * from user";
            ResultSet rs = myPooledConnection.query(sql);
            try {
                while (rs.next()) {
                    System.out.println(rs.getString("username") + "使用管道" + myPooledConnection.getConn());
                }
                myPooledConnection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

下載源碼,可到github地址----myDataSource
【參考資料】:
理解數據庫連接池底層原理之手寫實現

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章