「資料庫、資料庫連線池、資料來源」這些概念你真的理解了嗎?

2023-04-22 06:00:43

前言

我學習的過程中,對於連線池和資料來源分得不是很清楚,而且我發現有的人將資料庫等同於資料來源,或者將資料來源等同於連線池,實際上這些說法並不準確。

在某次工作中,同事 A 說道,這個資料來源不行,那麼換一個資料來源就可以了,結果我看他操作,原來是改寫了設定中的資料庫連線的 URL,當時我在想,這就是換資料來源了?我以為說是把 Druid 這個資料來源換掉。至於為什麼會這麼想,主要是因為有個 DruidDataSource

現在,搞清楚它們的區別不妨聽我說說,歡迎大家在評論區說出你的看法!

資料庫

一提到資料庫,大家都會想到 MySQL、Oracle、PostgreSQL 這些。我們也習慣這樣講:我這個專案的資料庫使用的是 MySQL,是吧。

實際上,嚴格來講,這些是資料庫管理系統(Database Management System,DBMS),它們是一種可以操作和管理資料庫(Database)的軟體。真正的資料庫是指儲存資料的倉庫,這些資料都是持久化儲存在計算機的硬碟上的。

比如 MySQL,我們在 MySQL 使用者端使用 CREATE DATABASE db_demo; 命令,這樣就建立了一個名為 db_demo 的資料庫。

我們可以使用 SHOW VARIABLES LIKE '%datadir'; 命令檢視資料庫存放在哪個地方。

資料庫連線池

那什麼是資料庫連線池呢?在說什麼是連線池之前,我們先說說什麼是連線(Connection、Connect)。

連線

在一開始學習 MySQL 的時候,我們通過 MySQL 的使用者端來連線上 MySQL 的伺服器端:

mysql -u root -p 123456

當出現如下輸出時,就說明我們成功連線上 MySQL 的伺服器端,接著就能輸入各種 SQL 語句了:

Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 81
Server version: 5.7.39 MySQL Community Server (GPL)

Copyright (c) 2000, 2022, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>

以上的連線,只是連線到 MySQL 伺服器端,並沒有指定連線到哪一個資料庫,當然我們可以通過 USE 資料庫名稱 來切換到指定的資料庫,後續的操作都是在該資料庫上進行。

此處的連線,是一個動作,即 Connect。

我們在學習 JDBC 的時候,知道了想要通過 Java 去運算元據庫,那麼就需要藉助 JDBC 來操作。

在這個過程中,我們首先需要載入資料庫的驅動,然後建立 Java 程式與某個資料庫的連線:

String driver = "com.mysql.jdbc.Driver";
String url = "jdbc:mysql://localhost:3306/db_demo";
String username = "root";
String password = "123456";
// 載入驅動
Class.forName(driver);
// 獲取該資料庫連線(即幫我們建立了一個可以操作db_demo的連線物件)
Connection conn = DriverManager.getConnection(url, username, password);

獲取完之後,我們就能通過連線獲取相關的 Statement 物件(比如預編譯的 PreparedStatement 物件),將我們的 SQL 語句丟給 Statement 物件,通過 Statement 物件執行操作。

操作完畢後就關閉了資料庫連線。

conn.close();

這裡的連線,是一個動作,也是一個物件,因為 Java 是物件導向的,抽象出了一個連線物件,這個連線物件包含了驅動資訊、連線的 URL、DBMS 的使用者名稱和密碼,主要表明了此次連線到的是哪個資料庫。

池化技術

現在,我們每進行一次相關的資料庫操作,就需要經過開啟/建立連線,執行相關操作,銷燬/關閉連線這麼一個過程。

對於一個簡單的、對資料庫的操作不是很頻繁的應用來說,問題不大,不會有很明顯的效能開銷。

但是,對於一個經常運算元據庫的應用來說,當有許多操作的請求過來時,多次的建立與銷燬連線物件,是相當耗費資源的,比如網路頻寬,記憶體,CPU 的運算等等。當然除了耗費資源,建立與銷燬也會耗費時間。所以就有了資料庫連線池的出現,這種是屬於「池化技術」

池化技術有這樣的特點,就是提前準備,然後進行復用。對於資料庫連線池,就是提前準備好一定量的連線物件放在一個「池子」中,你可以想象水池,有一定量的水。當有需要的時候,就從這個池子中獲取連線物件,然後進行資料庫的操作,操作完了後,就把物件放回池子中。這就是所謂的「資料庫連線池」

這裡也就有種用空間換時間的感覺,通過準備一定量的連線物件,避免由呼叫者手動去開啟和關閉連線,進而提高效率。

自己實現一個資料庫連線池

選擇你喜歡的一個地方新建一個類和一個組態檔,我將這兩個東西放在了同一個目錄下:

db.properties:

# 資料庫相關設定
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/db_one_demo
username=root
password=123456
# 初始化的資料庫連線池大小
initialPoolSize=5

ConnectionPool.java:

/**
 * @author god23bin
 * @description 簡單的資料庫連線池
 */
public class ConnectionPool {
    private static String driver;
    private static String url;
    private static String username;
    private static String password;

    /**
     * 使用一個List來存放連線,將這個List作為連線池
     **/
    private static List<Connection> connectionPool;

    /**
     * 標記對應的連線是否被使用,是為 true,否為 false
     **/
    private static List<Boolean> usedConnections;

    /**
     * 連線池大小,即池子中連線的個數
     **/
    private static int initialPoolSize;

    // 讀取組態檔只需要一次就夠了,所以用static程式碼塊
    static {
        //讀取檔案設定
        InputStream inputStream = null;
        Properties properties = new Properties();
        try {
            // 如果你的是 Spring Boot 應用,db.properties 放在 resource 目錄下,則可以通過 ClassPathResource 來獲取這個組態檔
            // inputStream = new ClassPathResource("db.properties").getInputStream();
            inputStream = ConnectionPool.class.getClassLoader().getResourceAsStream("db.properties");


            properties.load(inputStream);
            driver = properties.getProperty("driver");
            url = properties.getProperty("url");
            username = properties.getProperty("username");
            password = properties.getProperty("password");
            initialPoolSize = Integer.parseInt(properties.getProperty("initialPoolSize"));

            connectionPool = new ArrayList<>(initialPoolSize);
            usedConnections = new ArrayList<>(initialPoolSize);
            // 載入驅動
            Class.forName(driver);
            // 建立連線並將連線放到List集合中,標記為未被使用
            for (int i = 0; i < initialPoolSize; i++) {
                Connection connection = DriverManager.getConnection(url, username, password);
                connectionPool.add(connection);
                usedConnections.add(false);
            }

        } catch (IOException | SQLException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * 獲取連線
     * @return java.sql.Connection 返回連線物件
     **/
    public synchronized Connection getConnection() throws SQLException {
        // 判斷是否有空閒的連線可用,有的話就標記為使用中,接著返回這個連線
        for (int i = 0; i < initialPoolSize; i++) {
            if (!usedConnections.get(i)) {
                usedConnections.set(i, true);
                return connectionPool.get(i);
            }
        }

        // 如果沒有可用的連線,那麼建立一個新的連線,把它加入到池中,並返回,簡單處理,這裡的建立並沒有上限
        Connection connection = DriverManager.getConnection(url, username, password);
        connectionPool.add(connection);
        usedConnections.add(true);
        initialPoolSize++;
        return connection;
    }

    /**
     * 釋放連線,將其標記為未使用
     * @param connection 連線
     **/
    public synchronized void releaseConnection(Connection connection) {
        int index = connectionPool.indexOf(connection);
        usedConnections.set(index, false);
    }
}

目前我知道的開源的資料庫連線池有 DBCP、C3P0,還有阿里的 Druid。

資料來源

資料來源(Data Source),即資料的來源。在咱們開發的應用中,資料可以來源於網路,也可以來源於原生的檔案,還可以來源於資料庫。

簡而言之,資料來源指定了資料從哪裡來。換句話說,資料來源是指儲存資料的位置。

在 Java 中,有一個 javax.sql.DataSource 介面,這個介面定義了一組獲取資料庫連線的方法。

  • Connection getConnection() throws SQLException
  • Connection getConnection(String username, String password) throws SQLException

以上,就是所謂的資料來源。

資料來源和連線池的關係

有的人會把資料來源等同於連線池,那到底是不是呢?從概念上看,明顯不是一個東西,資料來源是資料來源,連線池則是連線的快取池,用於儲存和管理資料庫連線。

我認為,出現這種看法是因為我們在設定資料來源的時候,把連線池也進行了相關的設定。所以才會把資料來源等同於連線池。

不過,雖然不是同個東西,但是資料來源和連線池是緊密相關的,它們一起協同工作來管理資料庫連線並提供存取資料庫的功能。

在日常開發中,資料來源除了指資料來自哪裡,還可以有其他資訊!對於資料來源物件來說,它定義了資料庫連線引數以及連線資料庫所需的所有資訊,例如資料庫伺服器的地址、使用者名稱和密碼等。

有的連線池,會從資料來源物件中獲取連線引數並使用它們來建立和管理資料庫連線,就比如當我們在專案中使用開源的資料庫連線池的時候,就需要進行相關的設定。對於開源的資料庫連線池,它們都具有實現 Java 的標準資料來源介面 javax.sql.DataSource 的類,可以直接使用。

以 Druid 為例,這個類是 com.alibaba.druid.pool.DruidDataSource,可以用於建立和管理資料庫連線。

在我看來,Druid 是一個既包含資料來源功能又包含連線池功能的開源專案。它可以用作資料來源,通過設定和管理連線池來提供存取資料庫的功能。Druid 提供了一組高效的連線池和監控工具,可用於管理和監控資料庫連線。

https://github.com/alibaba/druid/wiki/DruidDataSource設定

這裡給出 Druid 的一些設定:

 <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> 
     <!-- 基本屬性 url、user、password -->
     <property name="url" value="${jdbc_url}" />
     <property name="username" value="${jdbc_user}" />
     <property name="password" value="${jdbc_password}" />

     <!-- 設定初始化大小、最小、最大 -->
     <property name="initialSize" value="5" />
     <property name="minIdle" value="10" />
     <property name="maxActive" value="20" />
     
     <!-- 設定間隔多久才進行一次檢測,檢測需要關閉的空閒連線,單位是毫秒 -->
     <property name="timeBetweenEvictionRunsMillis" value="2000" />
     
     <!-- 設定一個連線在池中最小生存的時間,單位是毫秒 -->
     <property name="minEvictableIdleTimeMillis" value="600000" />
     <property name="maxEvictableIdleTimeMillis" value="900000" />

 </bean>

從上面的設定中可以看到,我們在這裡進行了資料來源設定,這裡不僅僅設定了連線物件連線的是哪一個資料庫,它的使用者名稱和使用者密碼是多少,還設定了資料庫連線池的初始化大小、最小和最大連線數等屬性。

總結

一開始,我主要說明了資料庫和資料庫管理系統(DBMS)的區別。雖然我們通常會將 MySQL、Oracle、PostgreSQL 等軟體稱為資料庫,但它們實際上是一種可以操作和管理資料庫的軟體,而真正的資料庫是指儲存資料的倉庫。

接著,講了什麼是資料庫連線池,資料庫連線池是一種池化技術,它可以提前準備好一定數量的連線物件並將它們放在一個「池子」中。這些連線物件可以被重複使用,當需要進行資料庫操作時,可以從連線池中獲取連線物件並執行相關操作。執行完畢後,連線物件會被放回到池子中,以供後續的操作使用。這種技術可以避免頻繁地建立和銷燬連線物件,從而提高應用程式的效能和效率。

最後,講了什麼是資料來源以及它與連線池的關係,它們兩者是不同的,或者說有這麼三個詞:「資料來源」、「資料庫連線池」、「資料來源物件」。單獨說資料來源,那麼就顧名思義,資料的來源。資料庫連線池,用於管理連線的,方便連線的複用,提升效率。資料來源物件,它包含了連線池的設定,也設定了資料來源,即資料從哪裡來。

以上,也不知道我又沒有說清楚,歡迎大家評論!

最後的最後

希望各位螢幕前的靚仔靚女們給個三連!你輕輕地點了個贊,那將在我的心裡世界增添一顆明亮而耀眼的星!

咱們下期再見!