JDBC核心技術(shù) [2] 事務 DAO 連接池 Apache-DBUtils 等

第6章: 數(shù)據(jù)庫事務

6.1 數(shù)據(jù)庫事務介紹
  • 事務:一組邏輯操作單元,使數(shù)據(jù)從一種狀態(tài)變換到另一種狀態(tài)是钥。

  • 事務處理(事務操作):保證所有事務都作為一個工作單元來執(zhí)行涕烧,即使出現(xiàn)了故障,都不能改變這種執(zhí)行方式碌嘀。當在一個事務中執(zhí)行多個操作時秃励,要么所有的事務都被提交(commit),那么這些修改就永久地保存下來围辙;要么數(shù)據(jù)庫管理系統(tǒng)將放棄所作的所有修改我碟,整個事務回滾(rollback)到最初狀態(tài)。

  • 為確保數(shù)據(jù)庫中數(shù)據(jù)的一致性姚建,數(shù)據(jù)的操縱應當是離散的成組的邏輯單元:當它全部完成時矫俺,數(shù)據(jù)的一致性可以保持,而當這個單元中的一部分操作失敗,整個事務應全部視為錯誤恳守,所有從起始點以后的操作應全部回退到開始狀態(tài)考婴。

6.2 JDBC事務處理
  • 數(shù)據(jù)一旦提交,就不可回滾催烘。

  • 數(shù)據(jù)什么時候意味著提交沥阱?

    • 當一個連接對象被創(chuàng)建時,默認情況下是自動提交事務:每次執(zhí)行一個 SQL 語句時伊群,如果執(zhí)行成功考杉,就會向數(shù)據(jù)庫自動提交,而不能回滾舰始。
    • 關(guān)閉數(shù)據(jù)庫連接崇棠,數(shù)據(jù)就會自動的提交。如果多個操作丸卷,每個操作使用的是自己單獨的連接枕稀,則無法保證事務。即同一個事務的多個操作必須在同一個連接下谜嫉。
  • JDBC程序中為了讓多個 SQL 語句作為一個事務執(zhí)行:

    • 調(diào)用 Connection 對象的 setAutoCommit(false); 以取消自動提交事務
    • 在所有的 SQL 語句都成功執(zhí)行后萎坷,調(diào)用 commit(); 方法提交事務
    • 在出現(xiàn)異常時,調(diào)用 rollback(); 方法回滾事務

若此時 Connection 沒有被關(guān)閉沐兰,還可能被重復使用哆档,則需要恢復其自動提交狀態(tài) setAutoCommit(true)。尤其是在使用數(shù)據(jù)庫連接池技術(shù)時住闯,執(zhí)行close()方法前瓜浸,建議恢復自動提交狀態(tài)。

【案例:用戶AA向用戶BB轉(zhuǎn)賬100】

public class Test1 {
    /**
     * 通用的增加刪除修改
     *
     * @param sql  需要執(zhí)行的sql語句
     * @param objs 需要傳入的參數(shù)
     * @throws Exception
     */
    public static void update(Connection conn, String sql, Object... objs) {
        PreparedStatement prep = null;
        try {
            prep = conn.prepareStatement(sql);
            for (int i = 0; i < objs.length; i++) {
                prep.setObject(i + 1, objs[i]);
            }
            prep.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JdbcUtils.closeConnection(null, prep);
        }
    }

    @Test
    public void test2() {
        Connection conn = null;
        try {
            conn = JdbcUtils.getConnection();
            //取消數(shù)據(jù)的自動提交
            conn.setAutoCommit(false);
            String sql1 = "update user_table set balance=balance-100 where user = ?";
            String sql2 = "update user_table set balance=balance+100 where user = ?";
            update(conn, sql1, "AA");
            System.out.println(10 / 0);
            update(conn, sql2, "BB");
            System.out.println("轉(zhuǎn)賬成功");
            //提交事務
            conn.commit();
        } catch (Exception e) {
            try {
                //回滾數(shù)據(jù)
                conn.rollback();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
            e.printStackTrace();
        } finally {
            try {
                //修改為自動提交數(shù)據(jù)
                //主要針對 使用數(shù)據(jù)庫連接池的使用
                conn.setAutoCommit(true);
            } catch (SQLException e) {
                e.printStackTrace();
            }
            JdbcUtils.closeConnection(conn, null);
        }
    }
}
6.3 事務的ACID屬性
  1. 原子性(Atomicity)
    原子性是指事務是一個不可分割的工作單位比原,事務中的操作要么都發(fā)生插佛,要么都不發(fā)生。

  2. 一致性(Consistency)
    事務必須使數(shù)據(jù)庫從一個一致性狀態(tài)變換到另外一個一致性狀態(tài)量窘。

  3. 隔離性(Isolation)
    事務的隔離性是指一個事務的執(zhí)行不能被其他事務干擾朗涩,即一個事務內(nèi)部的操作及使用的數(shù)據(jù)對并發(fā)的其他事務是隔離的,并發(fā)執(zhí)行的各個事務之間不能互相干擾绑改。

  4. 持久性(Durability)
    持久性是指一個事務一旦被提交谢床,它對數(shù)據(jù)庫中數(shù)據(jù)的改變就是永久性的,接下來的其他操作和數(shù)據(jù)庫故障不應該對其有任何影響厘线。

6.3.1 數(shù)據(jù)庫的并發(fā)問題
  • 對于同時運行的多個事務, 當這些事務訪問數(shù)據(jù)庫中相同的數(shù)據(jù)時, 如果沒有采取必要的隔離機制, 就會導致各種并發(fā)問題:

    • 臟讀: 對于兩個事務 T1, T2, T1 讀取了已經(jīng)被 T2 更新但還沒有被提交的字段识腿。之后, 若 T2 回滾, T1讀取的內(nèi)容就是臨時且無效的。
    • 不可重復讀: 對于兩個事務T1, T2, T1 讀取了一個字段, 然后 T2 更新了該字段造壮。之后, T1再次讀取同一個字段, 值就不同了渡讼。
    • 幻讀: 對于兩個事務T1, T2, T1 從一個表中讀取了一個字段, 然后 T2 在該表中插入了一些新的行骂束。之后, 如果 T1 再次讀取同一個表, 就會多出幾行。
  • 數(shù)據(jù)庫事務的隔離性: 數(shù)據(jù)庫系統(tǒng)必須具有隔離并發(fā)運行各個事務的能力, 使它們不會相互影響, 避免各種并發(fā)問題成箫。

  • 一個事務與其他事務隔離的程度稱為隔離級別展箱。數(shù)據(jù)庫規(guī)定了多種事務隔離級別, 不同隔離級別對應不同的干擾程度, 隔離級別越高, 數(shù)據(jù)一致性就越好, 但并發(fā)性越弱。

6.3.2 四種隔離級別
  • 數(shù)據(jù)庫提供的4種事務隔離級別:


    數(shù)據(jù)庫的隔離級別
  • Oracle 支持的 2 種事務隔離級別:READ COMMITED, SERIALIZABLE蹬昌。 Oracle 默認的事務隔離級別為: READ COMMITED 混驰。

  • Mysql 支持 4 種事務隔離級別。Mysql 默認的事務隔離級別為: REPEATABLE READ皂贩。

6.3.3 在MySql中設(shè)置隔離級別
  • 每啟動一個 mysql 程序, 就會獲得一個單獨的數(shù)據(jù)庫連接. 每個數(shù)據(jù)庫連接都有一個全局變量 @@tx_isolation, 表示當前的事務隔離級別栖榨。

  • 查看當前的隔離級別:

SELECT @@tx_isolation;

設(shè)置當前 mySQL 連接的隔離級別:

set  transaction isolation level read committed;

設(shè)置數(shù)據(jù)庫系統(tǒng)的全局的隔離級別:

set global transaction isolation level read committed;

補充操作:

  • 創(chuàng)建mysql數(shù)據(jù)庫用戶:
create user tom identified by 'abc123';

授予權(quán)限

#授予通過網(wǎng)絡方式登錄的tom用戶,對所有庫所有表的全部權(quán)限明刷,密碼設(shè)為abc123.
grant all privileges on *.* to tom@'%'  identified by 'abc123'; 

 #給tom用戶使用本地命令行方式婴栽,授予atguigudb這個庫下的所有表的插刪改查的權(quán)限。
grant select,insert,delete,update on atguigudb.* to tom@localhost identified by 'abc123'; 

Java代碼設(shè)置數(shù)據(jù)庫的隔離級別

package cn.icanci.jdbc.transaction;

import cn.icanci.jdbc.domain.Customer;
import cn.icanci.jdbc.domain.User2;
import cn.icanci.jdbc.utils.JdbcUtils;
import jdk.nashorn.internal.scripts.JD;
import org.junit.Test;

import javax.crypto.Cipher;
import java.lang.reflect.Field;
import java.sql.*;

/**
 * @Author: icanci
 * @ProjectName: jdbc
 * @PackageName: cn.icanci.jdbc.transaction
 * @Date: Created in 2020/2/16 11:23
 * @ClassAction:
 */
public class Test1 {
    /**
     * 通用的增加刪除修改
     *
     * @param sql  需要執(zhí)行的sql語句
     * @param objs 需要傳入的參數(shù)
     * @throws Exception
     */
    public static void update(Connection conn, String sql, Object... objs) {
        PreparedStatement prep = null;
        try {
            prep = conn.prepareStatement(sql);
            for (int i = 0; i < objs.length; i++) {
                prep.setObject(i + 1, objs[i]);
            }
            prep.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JdbcUtils.closeConnection(null, prep);
        }
    }

    @Test
    public void test2() {
        Connection conn = null;
        try {
            conn = JdbcUtils.getConnection();
            //取消數(shù)據(jù)的自動提交
            conn.setAutoCommit(false);
            String sql1 = "update user_table set balance=balance-100 where user = ?";
            String sql2 = "update user_table set balance=balance+100 where user = ?";
            update(conn, sql1, "AA");
            System.out.println(10 / 0);
            update(conn, sql2, "BB");
            System.out.println("轉(zhuǎn)賬成功");
            //提交事務
            conn.commit();
        } catch (Exception e) {
            try {
                //回滾數(shù)據(jù)
                conn.rollback();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
            e.printStackTrace();
        } finally {
            try {
                //修改為自動提交數(shù)據(jù)
                //主要針對 使用數(shù)據(jù)庫連接池的使用
                conn.setAutoCommit(true);
            } catch (SQLException e) {
                e.printStackTrace();
            }
            JdbcUtils.closeConnection(conn, null);
        }
    }


    //使用Java代碼設(shè)置事務的隔離級別
    public static <T>T  getInstance(Connection conn, String sql,Class<T> clazz,Object... objs)throws Exception{
        PreparedStatement prep = conn.prepareStatement(sql);
        for (int i = 0; i < objs.length; i++) {
            prep.setObject(i + 1, objs[i]);
        }

        ResultSet resultSet = prep.executeQuery();
        //獲取結(jié)果集的元數(shù)據(jù)
        ResultSetMetaData metaData = resultSet.getMetaData();
        //獲取結(jié)果集的列數(shù)
        int columnCount = metaData.getColumnCount();

        if (resultSet.next()) {
            T t = clazz.newInstance();
            for (int i = 0; i < columnCount; i++) {
                Object value = resultSet.getObject(i + 1);
                //獲取每個列的名字
                String columnName = metaData.getColumnName(i + 1);
                //給Customer 指定 賦值
                Field declaredField = clazz.getDeclaredField(columnName);
                declaredField.setAccessible(true);
                declaredField.set(t, value);
            }
            return t;
        }
        resultSet.close();
        JdbcUtils.closeConnection(null,prep);
        return null;
    }


    @Test
    public void test4() throws Exception{

        Connection conn = JdbcUtils.getConnection();
        //獲取隔離級別
        System.out.println(conn.getTransactionIsolation());
        //設(shè)置隔離級別
        conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
        //取消自動提交
        conn.setAutoCommit(false);
        String sql = "select * from user_table where user = ?";
        try {
            User2 instance = getInstance(conn, sql, User2.class, "CC");
            System.out.println(instance);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
//            JdbcUtils.closeConnection(conn,null);
        }
    }

    @Test
    public void test5()throws Exception{
        Connection conn = JdbcUtils.getConnection();
        conn.setAutoCommit(false);
        String sql = "update user_table set balance = balance +100 where user = ?";
        update(conn,sql,"CC");
        Thread.sleep(2000);
        System.out.println("over");
        conn.commit();
    }
}

第7章:DAO及相關(guān)實現(xiàn)類 DAO設(shè)計模式

  • DAO:Data Access Object訪問數(shù)據(jù)信息的類和接口辈末,包括了對數(shù)據(jù)的CRUD(Create愚争、Retrival、Update挤聘、Delete)准脂,而不包含任何業(yè)務相關(guān)的信息。有時也稱作:BaseDAO
  • 作用:為了實現(xiàn)功能的模塊化檬洞,更有利于代碼的維護和升級。
此處 @DAO設(shè)計模式

第8章:數(shù)據(jù)庫連接池

8.1 JDBC數(shù)據(jù)庫連接池的必要性
  • 在使用開發(fā)基于數(shù)據(jù)庫的web程序時沟饥,傳統(tǒng)的模式基本是按以下步驟:

    • 在主程序(如servlet添怔、beans)中建立數(shù)據(jù)庫連接
    • 進行sql操作
    • 斷開數(shù)據(jù)庫連接
  • 這種模式開發(fā),存在的問題:

    • 普通的JDBC數(shù)據(jù)庫連接使用 DriverManager 來獲取贤旷,每次向數(shù)據(jù)庫建立連接的時候都要將 Connection 加載到內(nèi)存中广料,再驗證用戶名和密碼(得花費0.05s~1s的時間)。需要數(shù)據(jù)庫連接的時候幼驶,就向數(shù)據(jù)庫要求一個艾杏,執(zhí)行完成后再斷開連接。這樣的方式將會消耗大量的資源和時間盅藻。數(shù)據(jù)庫的連接資源并沒有得到很好的重復利用购桑。若同時有幾百人甚至幾千人在線,頻繁的進行數(shù)據(jù)庫連接操作將占用很多的系統(tǒng)資源氏淑,嚴重的甚至會造成服務器的崩潰勃蜘。
    • 對于每一次數(shù)據(jù)庫連接,使用完后都得斷開假残。否則缭贡,如果程序出現(xiàn)異常而未能關(guān)閉,將會導致數(shù)據(jù)庫系統(tǒng)中的內(nèi)存泄漏,最終將導致重啟數(shù)據(jù)庫阳惹。(回憶:何為Java的內(nèi)存泄漏谍失?)
    • 這種開發(fā)不能控制被創(chuàng)建的連接對象數(shù),系統(tǒng)資源會被毫無顧及的分配出去莹汤,如連接過多快鱼,也可能導致內(nèi)存泄漏,服務器崩潰体啰。
8.2 數(shù)據(jù)庫連接池技術(shù)
  • 為解決傳統(tǒng)開發(fā)中的數(shù)據(jù)庫連接問題攒巍,可以采用數(shù)據(jù)庫連接池技術(shù)。

  • 數(shù)據(jù)庫連接池的基本思想:就是為數(shù)據(jù)庫連接建立一個“緩沖池”荒勇。預先在緩沖池中放入一定數(shù)量的連接柒莉,當需要建立數(shù)據(jù)庫連接時,只需從“緩沖池”中取出一個沽翔,使用完畢之后再放回去兢孝。

  • 數(shù)據(jù)庫連接池負責分配、管理和釋放數(shù)據(jù)庫連接仅偎,它允許應用程序重復使用一個現(xiàn)有的數(shù)據(jù)庫連接跨蟹,而不是重新建立一個

  • 數(shù)據(jù)庫連接池在初始化時將創(chuàng)建一定數(shù)量的數(shù)據(jù)庫連接放到連接池中橘沥,這些數(shù)據(jù)庫連接的數(shù)量是由最小數(shù)據(jù)庫連接數(shù)來設(shè)定的窗轩。無論這些數(shù)據(jù)庫連接是否被使用,連接池都將一直保證至少擁有這么多的連接數(shù)量座咆。連接池的最大數(shù)據(jù)庫連接數(shù)量限定了這個連接池能占有的最大連接數(shù)痢艺,當應用程序向連接池請求的連接數(shù)超過最大連接數(shù)量時,這些請求將被加入到等待隊列中介陶。

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

    工作原理:
    工作原理

    數(shù)據(jù)庫連接池技術(shù)的優(yōu)點

1. 資源重用

由于數(shù)據(jù)庫連接得以重用堤舒,避免了頻繁創(chuàng)建,釋放連接引起的大量性能開銷哺呜。在減少系統(tǒng)消耗的基礎(chǔ)上舌缤,另一方面也增加了系統(tǒng)運行環(huán)境的平穩(wěn)性。

2. 更快的系統(tǒng)反應速度

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

3. 新的資源分配手段

對于多應用共享同一數(shù)據(jù)庫的系統(tǒng)而言椭豫,可在應用層通過數(shù)據(jù)庫連接池的配置耻瑟,實現(xiàn)某一應用最大可用數(shù)據(jù)庫連接數(shù)的限制旨指,避免某一應用獨占所有的數(shù)據(jù)庫資源

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

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

8.3 多種開源的數(shù)據(jù)庫連接池
  • JDBC 的數(shù)據(jù)庫連接池使用 javax.sql.DataSource 來表示框都,DataSource 只是一個接口搬素,該接口通常由服務器(Weblogic, WebSphere, Tomcat)提供實現(xiàn),也有一些開源組織提供實現(xiàn):
    • DBCP 是Apache提供的數(shù)據(jù)庫連接池魏保。tomcat 服務器自帶dbcp數(shù)據(jù)庫連接池熬尺。速度相對c3p0較快,但因自身存在BUG谓罗,Hibernate3已不再提供支持粱哼。
    • C3P0 是一個開源組織提供的一個數(shù)據(jù)庫連接池,速度相對較慢檩咱,穩(wěn)定性還可以揭措。hibernate官方推薦使用
    • Proxool 是sourceforge下的一個開源項目數(shù)據(jù)庫連接池,有監(jiān)控連接池狀態(tài)的功能刻蚯,穩(wěn)定性較c3p0差一點
    • BoneCP 是一個開源組織提供的數(shù)據(jù)庫連接池绊含,速度快
    • Druid 是阿里提供的數(shù)據(jù)庫連接池,據(jù)說是集DBCP 炊汹、C3P0 躬充、Proxool 優(yōu)點于一身的數(shù)據(jù)庫連接池,但是速度不確定是否有BoneCP快
  • DataSource 通常被稱為數(shù)據(jù)源讨便,它包含連接池和連接池管理兩個部分充甚,習慣上也經(jīng)常把 DataSource 稱為連接池
  • DataSource用來取代DriverManager來獲取Connection,獲取速度快器钟,同時可以大幅度提高數(shù)據(jù)庫訪問速度。
  • 特別注意:
    • 數(shù)據(jù)源和數(shù)據(jù)庫連接不同妙蔗,數(shù)據(jù)源無需創(chuàng)建多個傲霸,它是產(chǎn)生數(shù)據(jù)庫連接的工廠,因此整個應用只需要一個數(shù)據(jù)源即可眉反。
    • 當數(shù)據(jù)庫訪問結(jié)束后昙啄,程序還是像以前一樣關(guān)閉數(shù)據(jù)庫連接:conn.close(); 但conn.close()并沒有關(guān)閉數(shù)據(jù)庫的物理連接,它僅僅把數(shù)據(jù)庫連接釋放寸五,歸還給了數(shù)據(jù)庫連接池梳凛。
8.3.1 C3P0數(shù)據(jù)庫連接池
  • 獲取連接方式一
@Test
public void testC3p0()throws Exception{
    ComboPooledDataSource dataSource = new ComboPooledDataSource();
    dataSource.setDriverClass("com.mysql.jdbc.Driver");
    dataSource.setJdbcUrl("jdbc:mysql:///test");
    dataSource.setUser("root");
    dataSource.setPassword("ok");
    //設(shè)置初始的數(shù)據(jù)庫連接池個數(shù)
    dataSource.setInitialPoolSize(10);
    //設(shè)置最大的鏈接個數(shù)
    dataSource.setMaxPoolSize(100);

    Connection conn = dataSource.getConnection();
    System.out.println(conn);
}
  • 獲取連接方式二
private static DataSource cpds = new ComboPooledDataSource("helloc3p0");

@Test
public void testC3p0XML()throws Exception{
    //使用C3P0數(shù)據(jù)庫連接池的配置文件方式,獲取數(shù)據(jù)庫的連接:推薦
    Connection connection = cpds.getConnection();
    System.out.println(connection);
}
@Test
public void testC3p0XML()throws Exception{
    //使用C3P0數(shù)據(jù)庫連接池的配置文件方式梳杏,獲取數(shù)據(jù)庫的連接:推薦
    ComboPooledDataSource dataSource = new ComboPooledDataSource("helloc3p0");
    Connection connection = dataSource.getConnection();
    System.out.println(connection);
}

其中韧拒,配置文件為:c3p0-config.xml 名字不要修改

<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
    <named-config name="helloc3p0">
        <!-- 獲取連接的4個基本信息 -->
        <property name="user">root</property>
        <property name="password">ok</property>
        <property name="jdbcUrl">jdbc:mysql:///test</property>
        <property name="driverClass">com.mysql.jdbc.Driver</property>

        <!-- 涉及到數(shù)據(jù)庫連接池的管理的相關(guān)屬性的設(shè)置 -->
        <!-- 若數(shù)據(jù)庫中連接數(shù)不足時, 一次向數(shù)據(jù)庫服務器申請多少個連接 -->
        <property name="acquireIncrement">5</property>
        <!-- 初始化數(shù)據(jù)庫連接池時連接的數(shù)量 -->
        <property name="initialPoolSize">5</property>
        <!-- 數(shù)據(jù)庫連接池中的最小的數(shù)據(jù)庫連接數(shù) -->
        <property name="minPoolSize">5</property>
        <!-- 數(shù)據(jù)庫連接池中的最大的數(shù)據(jù)庫連接數(shù) -->
        <property name="maxPoolSize">10</property>
        <!-- C3P0 數(shù)據(jù)庫連接池可以維護的 Statement 的個數(shù) -->
        <property name="maxStatements">20</property>
        <!-- 每個連接同時可以使用的 Statement 對象的個數(shù) -->
        <property name="maxStatementsPerConnection">5</property>

    </named-config>
</c3p0-config>

抽取獲取和關(guān)閉連接的操作 使用c3p0連接池

package cn.icanci.jdbc.c3p0Utils;
import  java.sql.ResultSet;
import  java.sql.PreparedStatement;

import com.mchange.v2.c3p0.ComboPooledDataSource;

import java.sql.Connection;
import java.sql.SQLException;

/**
 * @Author: icanci
 * @ProjectName: jdbc
 * @PackageName: cn.icanci.jdbc.c3p0Utils
 * @Date: Created in 2020/2/16 14:20
 * @ClassAction:
 */
public class JdbcC3p0Utils {
    private static ComboPooledDataSource dataSource = new ComboPooledDataSource("helloc3p0");
    /**
     * 獲取連接
     * @return 返回 c3p0 連接池的連接
     * @throws Exception
     */
    public static Connection getConnection()  throws Exception{
        Connection conn = dataSource.getConnection();
        return conn;
    }

    public static void closeConnection(Connection conn,PreparedStatement ps,ResultSet rs){
        if (conn != null){
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (ps!=null){
            try {
                ps.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (rs != null){
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

8.3.2 DBCP數(shù)據(jù)庫連接池
  • DBCP 是 Apache 軟件基金組織下的開源連接池實現(xiàn)淹接,該連接池依賴該組織下的另一個開源系統(tǒng):Common-pool。如需使用該連接池實現(xiàn)叛溢,應在系統(tǒng)中增加如下兩個 jar 文件:
    • Commons-dbcp.jar:連接池的實現(xiàn)
    • Commons-pool.jar:連接池實現(xiàn)的依賴庫
  • Tomcat 的連接池正是采用該連接池來實現(xiàn)的塑悼。該數(shù)據(jù)庫連接池既可以與應用服務器整合使用,也可由應用程序獨立使用楷掉。
  • 數(shù)據(jù)源和數(shù)據(jù)庫連接不同厢蒜,數(shù)據(jù)源無需創(chuàng)建多個,它是產(chǎn)生數(shù)據(jù)庫連接的工廠烹植,因此整個應用只需要一個數(shù)據(jù)源即可斑鸦。
  • 當數(shù)據(jù)庫訪問結(jié)束后,程序還是像以前一樣關(guān)閉數(shù)據(jù)庫連接:conn.close(); 但上面的代碼并沒有關(guān)閉數(shù)據(jù)庫的物理連接草雕,它僅僅把數(shù)據(jù)庫連接釋放巷屿,歸還給了數(shù)據(jù)庫連接池。
  • 配置屬性說明
屬性 默認值 說明
initialSize 0 連接池啟動時創(chuàng)建的初始化連接數(shù)量
maxActive 8 連接池中可同時連接的最大的連接數(shù)
maxIdle 8 連接池中最大的空閑的連接數(shù)促绵,超過的空閑連接將被釋放攒庵,如果設(shè)置為負數(shù)表示不限制
minIdle 0 連接池中最小的空閑的連接數(shù),低于這個數(shù)量會被創(chuàng)建新的連接败晴。該參數(shù)越接近maxIdle浓冒,性能越好,因為連接的創(chuàng)建和銷毀尖坤,都是需要消耗資源的稳懒;但是不能太大。
maxWait 無限制 最大等待時間慢味,當沒有可用連接時场梆,連接池等待連接釋放的最大時間,超過該時間限制會拋出異常纯路,如果設(shè)置-1表示無限等待
poolPreparedStatements false 開啟池的Statement是否prepared
maxOpenPreparedStatements 無限制 開啟池的prepared 后的同時最大連接數(shù)
minEvictableIdleTimeMillis 連接池中連接持际,在時間段內(nèi)一直空閑偿短, 被逐出連接池的時間
removeAbandonedTimeout 300 超過時間限制,回收沒有用(廢棄)的連接
removeAbandoned false 超過removeAbandonedTimeout時間后,是否進 行沒用連接(廢棄)的回收
  • 獲取連接方式一:
@Test
public void test1()throws Exception{
    //創(chuàng)建了dbcp的連接池
    BasicDataSource dataSource = new BasicDataSource();
    dataSource.setDriverClassName("com.mysql.jdbc.Driver");
    dataSource.setUrl("jdbc:mysql:///test");
    dataSource.setUsername("root");
    dataSource.setPassword("ok");
    
    //設(shè)置其他的數(shù)據(jù)庫連接池的屬性
    dataSource.setInitialSize(10);
    dataSource.setMaxActive(10);
    Connection conn = dataSource.getConnection();
    System.out.println(conn);
}
  • 獲取連接方式二:
@Test
public void test2()throws Exception{
    Properties properties = new Properties();
    InputStream inputStream = DbcpTest.class.getClassLoader().getResourceAsStream("dbcp.properties");
    properties.load(inputStream);
    DataSource dataSource = BasicDataSourceFactory.createDataSource(properties);
    Connection connection = dataSource.getConnection();
    System.out.println(connection);
}

配置文件

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true&useServerPrepStmts=false
username=root
password=ok

initialSize=10
#...
8.3.3 Druid(德魯伊)數(shù)據(jù)庫連接池

Druid是阿里巴巴開源平臺上一個數(shù)據(jù)庫連接池實現(xiàn)展鸡,它結(jié)合了C3P0泼舱、DBCP杯活、Proxool等DB池的優(yōu)點缨恒,同時加入了日志監(jiān)控,可以很好的監(jiān)控DB池連接和SQL的執(zhí)行情況搓逾,可以說是針對監(jiān)控而生的DB連接池卷谈,可以說是目前最好的連接池之一。
使用方式1

@Test
public void test1() throws Exception{
    DruidDataSource dataSource = new DruidDataSource();
    dataSource.setDriverClassName("com.mysql.jdbc.Driver");
    dataSource.setUrl("jdbc:mysql:///test");
    dataSource.setUsername("root");
    dataSource.setPassword("ok");
    Connection conn = dataSource.getConnection();
    System.out.println(conn);
}

使用方式2

@Test
public void test2() throws Exception{
    Properties properties = new Properties();
    InputStream inputStream = DruidTest.class.getClassLoader().getResourceAsStream("druid.properties");
    properties.load(inputStream);
    DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
    Connection connection = dataSource.getConnection();
    System.out.println(connection);
}

配置文件 注意 :配置文件必須這樣寫 和官方手冊的一致 否則報錯

url=jdbc:mysql:///test
driverClassName=com.mysql.jdbc.Driver
username=root
password=ok
配置 缺省 說明
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。詳細看這里:https://github.com/alibaba/druid/wiki/%E4%BD%BF%E7%94%A8ConfigFilter
driverClassName 根據(jù)url自動識別 這一項可配可不配而昨,如果不配置druid會根據(jù)url自動識別dbType救氯,然后選擇相應的driverClassName(建議配置下)
initialSize 0 初始化時建立物理連接的個數(shù)。初始化發(fā)生在顯示調(diào)用init方法歌憨,或者第一次getConnection時
maxActive 8 最大連接池數(shù)量
maxIdle 8 已經(jīng)不再使用着憨,配置了也沒效果
minIdle 最小連接池數(shù)量
maxWait 獲取連接時最大等待時間,單位毫秒务嫡。配置了maxWait之后甲抖,缺省啟用公平鎖,并發(fā)效率會有所下降心铃,如果需要可以通過配置useUnfairLock屬性為true使用非公平鎖准谚。
poolPreparedStatements false 是否緩存preparedStatement,也就是PSCache去扣。PSCache對支持游標的數(shù)據(jù)庫性能提升巨大柱衔,比如說oracle。在mysql下建議關(guān)閉愉棱。
maxOpenPreparedStatements -1 要啟用PSCache唆铐,必須配置大于0,當大于0時奔滑,poolPreparedStatements自動觸發(fā)修改為true艾岂。在Druid中,不會存在Oracle下PSCache占用內(nèi)存過多的問題朋其,可以把這個數(shù)值配置大一些王浴,比如說100
validationQuery 用來檢測連接是否有效的sql,要求是一個查詢語句令宿。如果validationQuery為null叼耙,testOnBorrow腕窥、testOnReturn粒没、testWhileIdle都不會其作用。
testOnBorrow true 申請連接時執(zhí)行validationQuery檢測連接是否有效簇爆,做了這個配置會降低性能癞松。
testOnReturn false 歸還連接時執(zhí)行validationQuery檢測連接是否有效爽撒,做了這個配置會降低性能
testWhileIdle false 建議配置為true,不影響性能响蓉,并且保證安全性硕勿。申請連接的時候檢測,如果空閑時間大于timeBetweenEvictionRunsMillis枫甲,執(zhí)行validationQuery檢測連接是否有效源武。
timeBetweenEvictionRunsMillis 有兩個含義: 1)Destroy線程會檢測連接的間隔時間2)testWhileIdle的判斷依據(jù),詳細看testWhileIdle屬性的說明
numTestsPerEvictionRun 不再使用想幻,一個DruidDataSource只支持一個EvictionRun
minEvictableIdleTimeMillis
connectionInitSqls 物理連接初始化的時候執(zhí)行的sql
exceptionSorter 根據(jù)dbType自動識別 當數(shù)據(jù)庫拋出一些不可恢復的異常時粱栖,拋棄連接
filters 屬性類型是字符串,通過別名的方式配置擴展插件脏毯,常用的插件有: 監(jiān)控統(tǒng)計用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
proxyFilters 類型是List闹究,如果同時配置了filters和proxyFilters,是組合關(guān)系食店,并非替換關(guān)系

第9章:Apache-DBUtils實現(xiàn)CRUD操作

9.1 Apache-DBUtils簡介
  • commons-dbutils 是 Apache 組織提供的一個開源 JDBC工具類庫渣淤,它是對JDBC的簡單封裝,學習成本極低吉嫩,并且使用dbutils能極大簡化jdbc編碼的工作量价认,同時也不會影響程序的性能。

  • API介紹:

    • org.apache.commons.dbutils.QueryRunner
    • org.apache.commons.dbutils.ResultSetHandler
    • 工具類:org.apache.commons.dbutils.DbUtils
  • API包說明:


    API

    API
9.2 主要API的使用
9.2.1 DbUtils
  • DbUtils :提供如關(guān)閉連接率挣、裝載JDBC驅(qū)動程序等常規(guī)工作的工具類刻伊,里面的所有方法都是靜態(tài)的。主要方法如下:

    • public static void close(…) throws java.sql.SQLException: DbUtils類提供了三個重載的關(guān)閉方法椒功。這些方法檢查所提供的參數(shù)是不是NULL捶箱,如果不是的話动漾,它們就關(guān)閉Connection、Statement和ResultSet晨川。

    • public static void closeQuietly(…): 這一類方法不僅能在Connection、Statement和ResultSet為NULL情況下避免關(guān)閉删豺,還能隱藏一些在程序中拋出的SQLEeception。

    • public static void commitAndClose(Connection conn)throws SQLException: 用來提交連接的事務妈拌,然后關(guān)閉連接

    • public static void commitAndCloseQuietly(Connection conn): 用來提交連接,然后關(guān)閉連接蓬蝶,并且在關(guān)閉連接時不拋出SQL異常尘分。

    • public static void rollback(Connection conn)throws SQLException:允許conn為null,因為方法內(nèi)部做了判斷

    • public static void rollbackAndClose(Connection conn)throws SQLException

    • rollbackAndCloseQuietly(Connection)

    • public static boolean loadDriver(java.lang.String driverClassName):這一方裝載并注冊JDBC驅(qū)動程序著摔,如果成功就返回true定续。使用該方法私股,你不需要捕捉這個異常ClassNotFoundException。

9.2.2 QueryRunner類
  • 該類簡單化了SQL查詢庇茫,它與ResultSetHandler組合在一起使用可以完成大部分的數(shù)據(jù)庫操作旦签,能夠大大減少編碼量。

  • QueryRunner類提供了兩個構(gòu)造器:

    • 默認的構(gòu)造器

    • 需要一個 javax.sql.DataSource 來作參數(shù)的構(gòu)造器

  • QueryRunner類的主要方法:

    • 更新

      • public int update(Connection conn, String sql, Object... params) throws SQLException:用來執(zhí)行一個更新(插入偿曙、更新或刪除)操作羔巢。

      • ......

    • 插入

      • public <T> T insert(Connection conn,String sql,ResultSetHandler<T> rsh, Object... params) throws SQLException:只支持INSERT語句,其中 rsh - The handler used to create the result object from the ResultSet of auto-generated keys. 返回值: An object generated by the handler.即自動生成的鍵值

      • ....

    • 批處理

      • public int[] batch(Connection conn,String sql,Object params)throws SQLException: INSERT, UPDATE, or DELETE語句

      • public <T> T insertBatch(Connection conn,String sql,ResultSetHandler<T> rsh,Object params)throws SQLException:只支持INSERT語句

      • .....

    • 查詢

      • public Object query(Connection conn, String sql, ResultSetHandler rsh,Object... params) throws SQLException:執(zhí)行一個查詢操作启摄,在這個查詢中歉备,對象數(shù)組中的每個元素值被用來作為查詢語句的置換參數(shù)匪燕。該方法會自行處理 PreparedStatement 和 ResultSet 的創(chuàng)建和關(guān)閉。

      • ......

  • 測試 增加和刪除

public class dbutils {
    @Test
    public void test1() throws Exception {
        QueryRunner runner = new QueryRunner();
        Connection connection = JdbcUtils.getConnection();
        runner.update(connection, "insert into user_table (user,password,balance) values (?,?,?)", "哈希", "haxilove", 1255);
        JdbcUtils.closeConnection(connection,null);    
    }

    @Test
    public void test2() throws Exception {
        QueryRunner runner = new QueryRunner();
        Connection connection = JdbcUtils.getConnection();
        int haxi = runner.update(connection, "delete from user_table where user =?", "哈希");
        System.out.println(haxi);
        JdbcUtils.closeConnection(connection,null);
    }
}
9.2.3 ResultSetHandler接口及實現(xiàn)類
  • 該接口用于處理 java.sql.ResultSet龟再,將數(shù)據(jù)按要求轉(zhuǎn)換為另一種形式利凑。

  • ResultSetHandler 接口提供了一個單獨的方法:Object handle (java.sql.ResultSet .rs)。

  • 接口的主要實現(xiàn)類:

    • ArrayHandler:把結(jié)果集中的第一行數(shù)據(jù)轉(zhuǎn)成對象數(shù)組截碴。

    • ArrayListHandler:把結(jié)果集中的每一行數(shù)據(jù)都轉(zhuǎn)成一個數(shù)組日丹,再存放到List中蚯嫌。

    • BeanHandler:將結(jié)果集中的第一行數(shù)據(jù)封裝到一個對應的JavaBean實例中。

    • BeanListHandler:將結(jié)果集中的每一行數(shù)據(jù)都封裝到一個對應的JavaBean實例中束凑,存放到List里栅盲。

    • ColumnListHandler:將結(jié)果集中某一列的數(shù)據(jù)存放到List中。

    • KeyedHandler(name):將結(jié)果集中的每一行數(shù)據(jù)都封裝到一個Map里谈秫,再把這些map再存到一個map里,其key為指定的key该编。

    • MapHandler:將結(jié)果集中的第一行數(shù)據(jù)封裝到一個Map里硕淑,key是列名,value就是對應的值于樟。

    • MapListHandler:將結(jié)果集中的每一行數(shù)據(jù)都封裝到一個Map里,然后再存放到List

    • ScalarHandler:查詢單個值對象

  • 測試 查詢 查詢一個
@Test
public void test3() throws Exception {
    QueryRunner runner = new QueryRunner();
    Connection connection = JdbcUtils.getConnection();
    User2 query = runner.query(connection, "select * from user_table where user = ?", new BeanHandler<User2>(User2.class),"AA");
    System.out.println(query);
    JdbcUtils.closeConnection(connection,null);
}
  • 查詢 查詢多個
@Test
public void test4() throws Exception {
    QueryRunner runner = new QueryRunner();
    Connection connection = JdbcUtils.getConnection();
    List<User2> query = runner.query(connection, "select * from user_table ", new BeanListHandler<User2>(User2.class));
    query.forEach(System.out::println);
    JdbcUtils.closeConnection(connection,null);
}
/*
 * 自定義ResultSetHandler的實現(xiàn)類
 */
@Test
public void testQueryInstance1() throws Exception{
    QueryRunner runner = new QueryRunner();

    Connection conn = JDBCUtils.getConnection3();
        
    String sql = "select id,name,email,birth from customers where id = ?";
        
    ResultSetHandler<Customer> handler = new ResultSetHandler<Customer>() {

        @Override
        public Customer handle(ResultSet rs) throws SQLException {
            System.out.println("handle");
//          return new Customer(1,"Tom","tom@126.com",new Date(123323432L));
                
            if(rs.next()){
                int id = rs.getInt("id");
                String name = rs.getString("name");
                String email = rs.getString("email");
                Date birth = rs.getDate("birth");
                    
                return new Customer(id, name, email, birth);
            }
            return null;
                
        }
    };
        
    Customer customer = runner.query(conn, sql, handler, 23);
        
    System.out.println(customer);
        
    JDBCUtils.closeResource(conn, null);
}
/*
 * 如何查詢類似于最大的奢米,最小的鬓长,平均的涉波,總和,個數(shù)相關(guān)的數(shù)據(jù)苍日,
 * 使用ScalarHandler
 * 
 */
@Test
public void testQueryValue() throws Exception{
    QueryRunner runner = new QueryRunner();

    Connection conn = JDBCUtils.getConnection3();
        
    //測試一:
//  String sql = "select count(*) from customers where id < ?";
//  ScalarHandler handler = new ScalarHandler();
//  long count = (long) runner.query(conn, sql, handler, 20);
//  System.out.println(count);
        
    //測試二:
    String sql = "select max(birth) from customers";
    ScalarHandler handler = new ScalarHandler();
    Date birth = (Date) runner.query(conn, sql, handler);
    System.out.println(birth);
        
    JDBCUtils.closeResource(conn, null);
}

JDBC總結(jié)

總結(jié)
@Test
public void testUpdateWithTx() {
        
    Connection conn = null;
    try {
        //1.獲取連接的操作(
        //① 手寫的連接:JDBCUtils.getConnection();
        //② 使用數(shù)據(jù)庫連接池:C3P0;DBCP;Druid
        //2.對數(shù)據(jù)表進行一系列CRUD操作
        //① 使用PreparedStatement實現(xiàn)通用的增刪改窗声、查詢操作(version 1.0 \ version 2.0)
//version2.0的增刪改public void update(Connection conn,String sql,Object ... args){}
//version2.0的查詢 public <T> T getInstance(Connection conn,Class<T> clazz,String sql,Object ... args){}
        //② 使用dbutils提供的jar包中提供的QueryRunner類
            
        //提交數(shù)據(jù)
        conn.commit();
            
    
    } catch (Exception e) {
        e.printStackTrace();
            
            
        try {
            //回滾數(shù)據(jù)
            conn.rollback();
        } catch (SQLException e1) {
            e1.printStackTrace();
        }
            
    }finally{
        //3.關(guān)閉連接等操作
        //① JDBCUtils.closeResource();
        //② 使用dbutils提供的jar包中提供的DbUtils類提供了關(guān)閉的相關(guān)操作
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末笨觅,一起剝皮案震驚了整個濱河市见剩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌苍苞,老刑警劉巖羹呵,帶你破解...
    沈念sama閱讀 212,686評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異方援,居然都是意外死亡涛癌,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,668評論 3 385
  • 文/潘曉璐 我一進店門先匪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來呀非,“玉大人镜盯,你說我怎么就攤上這事速缆。” “怎么了艺糜?”我有些...
    開封第一講書人閱讀 158,160評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長尉剩。 經(jīng)常有香客問我毅臊,道長褂微,這世上最難降的妖魔是什么园爷? 我笑而不...
    開封第一講書人閱讀 56,736評論 1 284
  • 正文 為了忘掉前任童社,我火速辦了婚禮,結(jié)果婚禮上呀癣,老公的妹妹穿的比我還像新娘弦赖。我一直安慰自己蹬竖,他們只是感情好,可當我...
    茶點故事閱讀 65,847評論 6 386
  • 文/花漫 我一把揭開白布列另。 她就那樣靜靜地躺著旦装,像睡著了一般阴绢。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上响巢,一...
    開封第一講書人閱讀 50,043評論 1 291
  • 那天棒妨,我揣著相機與錄音,去河邊找鬼拘泞。 笑死枕扫,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的诗鸭。 我是一名探鬼主播参滴,決...
    沈念sama閱讀 39,129評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼砾赔,長吁一口氣:“原來是場噩夢啊……” “哼暴心!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起悯衬,我...
    開封第一講書人閱讀 37,872評論 0 268
  • 序言:老撾萬榮一對情侶失蹤檀夹,失蹤者是張志新(化名)和其女友劉穎击胜,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體暇唾,經(jīng)...
    沈念sama閱讀 44,318評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡策州,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,645評論 2 327
  • 正文 我和宋清朗相戀三年宫仗,在試婚紗的時候發(fā)現(xiàn)自己被綠了藕夫。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片枯冈。...
    茶點故事閱讀 38,777評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡尘奏,死狀恐怖病蛉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情俗孝,我是刑警寧澤赋铝,帶...
    沈念sama閱讀 34,470評論 4 333
  • 正文 年R本政府宣布诀艰,位于F島的核電站其垄,受9級特大地震影響卤橄,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜喇颁,卻給世界環(huán)境...
    茶點故事閱讀 40,126評論 3 317
  • 文/蒙蒙 一橘霎、第九天 我趴在偏房一處隱蔽的房頂上張望殖属。 院中可真熱鬧,春花似錦外潜、人聲如沸挠唆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,861評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嵌灰。三九已至沽瞭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間城丧,已是汗流浹背豌鹤。 一陣腳步聲響...
    開封第一講書人閱讀 32,095評論 1 267
  • 我被黑心中介騙來泰國打工布疙, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人截型。 一個月前我還...
    沈念sama閱讀 46,589評論 2 362
  • 正文 我出身青樓宦焦,卻偏偏與公主長得像顿涣,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子精堕,可洞房花燭夜當晚...
    茶點故事閱讀 43,687評論 2 351

推薦閱讀更多精彩內(nèi)容