數(shù)據(jù)庫連接池

一、數(shù)據(jù)庫連接池

1.1 什么是數(shù)據(jù)庫連接池

連接池是Connection對象的緩沖區(qū),它里面會存放一些Connection,當(dāng)我們Java程序需要使用Connection的時候,如果連接池中有則直接從連接池獲取,不需要去新創(chuàng)建Connection了。連接池讓Java程序能夠復(fù)用連接、管理連接。

1.2 為什么要使用連接池

  • 因為每次創(chuàng)建和銷毀連接都會帶來較大的系統(tǒng)開銷
  • 每次創(chuàng)建和銷毀連接都要消耗大概0.05~1s的時間
  • 可以防止大量用戶并發(fā)訪問數(shù)據(jù)庫服務(wù)器

1.3 連接池的優(yōu)勢

1.3.1 資源重用

由于數(shù)據(jù)庫連接得到重用,避免了頻繁創(chuàng)建、釋放連接引起的大量性能開銷。在減少系統(tǒng)開銷的基礎(chǔ)之上,也增進(jìn)了系統(tǒng)運行環(huán)境的穩(wěn)定性。

1.3.2 更快的系統(tǒng)響應(yīng)速度

數(shù)據(jù)庫連接池在初始化過程中,往往已經(jīng)創(chuàng)建了若干數(shù)據(jù)庫連接置于池中備用。此時連接的初始化工作均已完成。對于業(yè)務(wù)請求處理而言,直接利用現(xiàn)有可用連接,避免了數(shù)據(jù)庫連接初始化和釋放過程的時間開銷,從而縮減了系統(tǒng)整體響應(yīng)時間。

1.3.3 新的資源分配手段

對于多應(yīng)用共享同一數(shù)據(jù)庫而言,可在應(yīng)用層通過數(shù)據(jù)庫連接的配置。

1.3.4 統(tǒng)一的連接管理,避免數(shù)據(jù)庫連接泄露

在較為完備的數(shù)據(jù)庫連接池實現(xiàn)中,可根據(jù)預(yù)先的連接占用超時設(shè)定,強制收回被占用連接。從而避免了常規(guī)數(shù)據(jù)庫連接操作中可能出現(xiàn)的資源泄露。

1.4 連接池的原理

  1. 連接池維護(hù)者兩個容器空閑池活動池。
  2. 空閑池用于存放未使用的連接,活動池用于存放正在使用中的連接,活動池中的連接使用完之后要歸還回空閑池。
  3. 當(dāng)Java程序需要連接的時候,先要判斷空閑池中是否有連接,如果空閑池中有連接則取出一個連接放置到活動池供Java程序使用。
  4. Java程序需要連接的時候,如果空閑池中沒有連接了,就先判斷活動池的連接數(shù)是否已經(jīng)達(dá)到了最大連接數(shù),如果未達(dá)到最大連接數(shù),則會新創(chuàng)建一個連接放置到活動池,供Java程序使用。
  5. 如果空閑池中沒有連接了,活動池中的連接也已經(jīng)達(dá)到了最大連接數(shù),就不能再新創(chuàng)建連接了,此時會判斷是否等待超時,如果沒有等待超時就需要等待活動池中的連接歸還回空閑池
  6. 如果等待超時了,則可以采取多種處理方式。如:直接拋出超時異常,或者將活動池中使用最久的連接移除掉歸還回空閑池以供Java程序使用。

1.5 連接池的實現(xiàn)

1.5.1 DataSource接口

JDBC的數(shù)據(jù)庫連接池使用javax.sql.DataSource來表示,DataSource只是一個接口,所有的Java數(shù)據(jù)庫連接池都需要實現(xiàn)該接口。該接口通常由服務(wù)器(Weblogic,WebSphere,Tomcat)提供實現(xiàn),也有一些開源組織提供實現(xiàn)。

1.5.2 常見的數(shù)據(jù)庫連接池

  • DBCP是Apache提供的數(shù)據(jù)庫連接池,速度相對c3p0較快,但因為其自身存在BUG,Hibernate3已不再提供支持。
  • C3P0是一個開源組織提供的一個數(shù)據(jù)庫連接池,速度相對較慢,穩(wěn)定性還行
  • Proxool是sourceforge下的一個開源項目數(shù)據(jù)庫連接池,有監(jiān)控連接池狀態(tài)的功能,穩(wěn)定性較c3p0差一點。
    • HikariCP俗稱光連接池,是目前速度最快的連接池。
  • Druid阿里提供的額數(shù)據(jù)庫連接池,據(jù)說是集DBCP、C3P0、Proxool優(yōu)點于一身的數(shù)據(jù)庫連接池。

1.5.3 Druid連接池的使用

  1. 導(dǎo)入Druid的jar包
  2. 創(chuàng)建Druid連接池的配置文件druid.properties文件,放置到resources文件夾下
  3. 代碼中使用
    public static void test01() throws Exception{
        // 1. 創(chuàng)建一個Properties對象,讓其去讀取druid.properties文件
        Properties properties = new Properties();
        // 1.1 將druid.properties配置文件轉(zhuǎn)成字節(jié)輸入流
//        FileInputStream is = new FileInputStream("絕對路徑");

        // 1.2 使用相對路徑來將配置文件轉(zhuǎn)成字節(jié)輸入流,可以使用類加載器來讀取類路徑下文件
        // 獲取ClassLoader對象
        ClassLoader classLoader = TestDataSource.class.getClassLoader();
        InputStream is = classLoader.getResourceAsStream("druid.properties");

        // 1.2 使用properties對象加載流
        properties.load(is);
        // 2. 使用創(chuàng)建DruidDataSourceFactory創(chuàng)建Druid的連接池對象
        DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);

        // 3. 從連接池中取出一個連接使用
        Connection connection1 = dataSource.getConnection();
        Connection connection2 = dataSource.getConnection();
        Connection connection3 = dataSource.getConnection();
        Connection connection4 = dataSource.getConnection();
        Connection connection5 = dataSource.getConnection();
        Connection connection6 = dataSource.getConnection();
        Connection connection7 = dataSource.getConnection();
        Connection connection8 = dataSource.getConnection();
        Connection connection9 = dataSource.getConnection();

        // 歸還一個連接:沒有使用連接池的時候connection.cloase()就是銷毀連接;如果是從連接池中取出的connection對象,它調(diào)用close()方法就是將連接歸還回連接池
        // 底層原理是動態(tài)代理
        connection1.close();

        Connection connection10 = dataSource.getConnection();
        Connection connection11 = dataSource.getConnection();
    }

1.5.4 Druid連接池的配置參數(shù)

配置 說明
name 配置這個屬性的意義在于,如果存在多個數(shù)據(jù)源,監(jiān)控的時候可以通過名字來區(qū)分開來。 如果沒有配置,將會生成一個名字,格式是:”DataSource-” + System.identityHashCode(this)
url 連接數(shù)據(jù)庫的url,不同數(shù)據(jù)庫不一樣。例如:mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto
username 連接數(shù)據(jù)庫的用戶名
password 連接數(shù)據(jù)庫的密碼。如果你不希望密碼直接寫在配置文件中,可以使用ConfigFilter。
driverClassName 根據(jù)url自動識別 這一項可配可不配,如果不配置druid會根據(jù)url自動識別dbType,然后選擇相應(yīng)的driverClassName(建議配置下)
initialSize 初始化時建立物理連接的個數(shù)。初始化發(fā)生在顯示調(diào)用init方法,或者第一次getConnection時
maxActive 最大連接池數(shù)量
minIdle 最小連接池數(shù)量
maxWait 獲取連接時最大等待時間,單位毫秒。配置了maxWait之后,缺省啟用公平鎖,并發(fā)效率會有所下降,如果需要可以通過配置useUnfairLock屬性為true使用非公平鎖。
poolPreparedStatements 是否緩存preparedStatement,也就是PSCache。PSCache對支持游標(biāo)的數(shù)據(jù)庫性能提升巨大,比如說oracle。在mysql下建議關(guān)閉。
maxOpenPreparedStatements 要啟用PSCache,必須配置大于0,當(dāng)大于0時,poolPreparedStatements自動觸發(fā)修改為true。在Druid中,不會存在Oracle下PSCache占用內(nèi)存過多的問題,可以把這個數(shù)值配置大一些,比如說100
validationQuery 用來檢測連接是否有效的sql,要求是一個查詢語句。如果validationQuery為null,testOnBorrow、testOnReturn、testWhileIdle都不會其作用。
testOnBorrow 申請連接時執(zhí)行validationQuery檢測連接是否有效,做了這個配置會降低性能。
testOnReturn 歸還連接時執(zhí)行validationQuery檢測連接是否有效,做了這個配置會降低性能
testWhileIdle 建議配置為true,不影響性能,并且保證安全性。申請連接的時候檢測,如果空閑時間大于timeBetweenEvictionRunsMillis,執(zhí)行validationQuery檢測連接是否有效。
timeBetweenEvictionRunsMillis 有兩個含義: 1)Destroy線程會檢測連接的間隔時間2)testWhileIdle的判斷依據(jù),詳細(xì)看testWhileIdle屬性的說明
connectionInitSqls 物理連接初始化的時候執(zhí)行的sql
exceptionSorter 根據(jù)dbType自動識別 當(dāng)數(shù)據(jù)庫拋出一些不可恢復(fù)的異常時,拋棄連接
filters 屬性類型是字符串,通過別名的方式配置擴展插件,常用的插件有: 監(jiān)控統(tǒng)計用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
proxyFilters 類型是List,如果同時配置了filters和proxyFilters,是組合關(guān)系,并非替換關(guān)系

1.5.5 封裝JDBCTools

/**
 * 這個工具類會提供三個方法:
 * 1. 獲取連接池對象
 * 2. 從連接池中獲取連接
 * 3. 將連接歸還到連接池
 */
public class JDBCTools {

    public static DataSource dataSource;
    static {
        try {
            // 1.使用類加載器讀取配置文件,轉(zhuǎn)成字節(jié)輸入流
            InputStream is = JDBCTools.class.getClassLoader().getResourceAsStream("druid.properties");
            // 2. 使用Properties對象加載字節(jié)輸入流
            Properties properties = new Properties();
            properties.load(is);
            // 3. DruidDataSourceFactory創(chuàng)建連接池對象
            dataSource = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 獲取連接池對象
     * @return
     */
    public static DataSource getDataSource(){
        return dataSource;
    }


    /**
     * 獲取連接
     * @return
     */
    public static Connection getConnection(){
        try {
            return dataSource.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
            // 運行時發(fā)生異常再拋出一場
            throw new RuntimeException(e.getMessage());
        }
    }

    /**
     * 將連接歸還連接池
     * @param connection
     * @throws SQLException
     */
    public static void releaseConnection(Connection connection) {
        try {
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException(e.getMessage());
        }
    }
}

二、Apache的DBUtils

2.1 DBUtils的概述

commons-dbutils是Apache組織提供的一個開源JDBC工具類庫,它是對JDBC的簡單封裝,學(xué)習(xí)成本極低,并且使用dbutils能極大簡化JDBC編碼的工作量,同時也不會影響程序的性能。

其中QueryRunner類封裝了SQL的執(zhí)行,是線程安全的。

  1. 可以實現(xiàn)增刪改查、批處理
  2. 考慮了事務(wù)處理需要共用Connection
  3. 該類最主要的就是簡單化了SQL查詢,它與ResultSetHandler組合在一起使用可以完成大部分的數(shù)據(jù)庫操作,能夠大大減少編碼量。

2.2 DBUtils執(zhí)行增刪改的SQL語句

    public static void testInsert() throws SQLException {
        // 第一種:需要在執(zhí)行sql語句的時候手動傳入連接對象

//        // 往user表中添加一行數(shù)據(jù)
//        // 1. 創(chuàng)建QueryRunner對象
//        QueryRunner queryRunner = new QueryRunner();
//        // 2. 調(diào)用queryRunner對象的方法執(zhí)行sql語句,如果是執(zhí)行增刪改的SQL語句則調(diào)用update方法
//        String sql = "insert into user(username,password,nickname) values (?,?,?)";
//        queryRunner.update(JDBCTools.getConnection(),sql,"黃忠","111111","千鈞一發(fā)");

        //第二種:直接將連接池對象交給QueryRunner
        QueryRunner queryRunner = new QueryRunner(JDBCTools.getDataSource());
        String sql = "insert into user(username,password,nickname) values (?,?,?)";
        queryRunner.update(sql,"趙云","123456","七進(jìn)七出");
    }

    public static void testDelete() throws SQLException {
        // 1. 創(chuàng)建QueryRunner對象
        QueryRunner queryRunner = new QueryRunner(JDBCTools.getDataSource());

        String sql = "delete from user where id=?";
        queryRunner.update(sql,2000);
    }

    public static void testUpdate() throws SQLException {
        // 將id為5的用戶昵稱改為張飛
        QueryRunner queryRunner = new QueryRunner(JDBCTools.getDataSource());

        String sql = "update user set nickname=? where id=? ";

        queryRunner.update(sql,"張飛",6);
    }

2.3 DBUtils執(zhí)行批處理

 public static void testBatchAdd() throws SQLException {
        // 測試批量添加

        QueryRunner queryRunner = new QueryRunner(JDBCTools.getDataSource());
        // 編寫SQL語句
        String sql = "insert into user (username,password,nickname) values (?,?,?)";

        // 創(chuàng)建一個二維數(shù)組,用于存儲批量參數(shù),二維數(shù)組的第一維表示批量操作多少條數(shù)據(jù),第二維表示每條數(shù)據(jù)要設(shè)置多少個參數(shù)
        Object[][] params = new Object[1000][3];
        // 循環(huán)設(shè)置批量添加的數(shù)據(jù)
        for (int i = 0; i < 1000; i++) {
            params[i][0] = "關(guān)羽"+i;
            params[i][1]="444444"+i;
            params[i][2] = "武圣"+i;
        }

        // 執(zhí)行批量處理
        queryRunner.batch(sql,params);
    }

2.4 使用QueryRunner類實現(xiàn)查詢

2.4.1 Handler類型介紹

Handler類型 說明
ArrayHandler 將結(jié)果集中的第一條記錄封裝到一個Object[]數(shù)組中,數(shù)組中的每一個元素就是這條記錄中的每一個字段的值
ArrayListHandler 將結(jié)果集中的每一條記錄都封裝到一個Object[]數(shù)組中,將這些數(shù)組在封裝到List集合中。
BeanHandler 將結(jié)果集中第一條記錄封裝到一個指定的javaBean中。
BeanListHandler 將結(jié)果集中每一條記錄封裝到指定的javaBean中,將這些javaBean在封裝到List集合中
ColumnListHandler 將結(jié)果集中指定的列的字段值,封裝到一個List集合中
KeyedHandler 將結(jié)果集中每一條記錄封裝到Map<String,Object>,在將這個map集合做為另一個Map的value,另一個Map集合的key是指定的字段的值。
MapHandler 將結(jié)果集中第一條記錄封裝到了Map<String,Object>集合中,key就是字段名稱,value就是字段值
MapListHandler 將結(jié)果集中每一條記錄封裝到了Map<String,Object>集合中,key就是字段名稱,value就是字段值,在將這些Map封裝到List集合中。
ScalarHandler 它是用于單個數(shù)據(jù)。例如select count(*) from 表。

2.4.2 代碼示例

public static void testFindById() throws SQLException {
        // 使用DBUtils執(zhí)行查詢一行數(shù)據(jù)
        // 查詢一行數(shù)據(jù)轉(zhuǎn)換成User對象
        QueryRunner queryRunner = new QueryRunner(JDBCTools.getDataSource());
        // 編寫SQL語句
        String sql = "select * from user where id=?";
        // queryRunner執(zhí)行查詢的SQL語句,使用query()方法
        User user = queryRunner.query(sql, new BeanHandler<>(User.class), 1);
        System.out.println(user);
    }

    public static void testFindMore() throws SQLException {
        // 查詢多條數(shù)據(jù)轉(zhuǎn)換成User對象
        QueryRunner queryRunner = new QueryRunner(JDBCTools.getDataSource());
        // 編寫SQL語句
        String sql = "select * from user where id<?";
        List<User> userList = queryRunner.query(sql, new BeanListHandler<>(User.class), 10);

        for (int i = 0; i < userList.size(); i++) {
            System.out.println(userList.get(i));
        }
    }

    public static void testFindCount() throws SQLException {
        // 目標(biāo):查詢總用戶數(shù)
        QueryRunner queryRunner = new QueryRunner(JDBCTools.getDataSource());
        String sql = "select count(id) from user";

        // ScalarHandler是處理單個數(shù)據(jù)的結(jié)果集
        Long count = queryRunner.query(sql, new ScalarHandler<>());

        System.out.println(count);
    }

三、jar包版本不兼容問題

  1. 使用最新的MySQL驅(qū)動jar包
  2. 把驅(qū)動的類名改為:com.mysql.cj.jdbc.Driver
  3. 在訪問mysql的url后加入時區(qū)設(shè)置:jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF8&serverTimezone=UTC

學(xué)海無涯苦作舟

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容