【很全很新】C3P0 連線池和 DBUtils 配合事務使用總結

愛看美劇真是太好了發表於2018-12-06

很久沒用原生連線池,最近想寫個小功能,結果發現很多地方不太懂,然後網上搜了半天的 c3p0 相關內容,全不符合我想要的。相同內容太多 而且沒什麼,所以我自己來總結下吧。

01 總結全文

從以下來總結

  • 連線池的作用,為什麼要使用連線池
  • 書寫自己的連線池,用於理解框架 c3p0 等連線池
  • 連線池框架 c3p0 使用
  • 連線池框架 c3p0 和 DBUtils 的配合使用
  • 配合事務的使用(重點,這塊很多人都說不明白)

02 分析

0201 連線池的作用,為什麼要使用連線池

首先我們運算元據庫都需要連線,平時獲取連線、關閉連線如果頻繁,就會浪費資源,佔用CPU。所以這裡我們用一個池子來存放連線。 先自定義一個自己的連線池,這些內容太簡單,我直接上程式碼。相信大家很容易看懂。

public class CustomConnectionUtils {

    private static LinkedList<Connection>  pool = new LinkedList<Connection>();

    /**
     * 初始化連線池 新增3個連線到池子中去
     */
    static {
        try {
            Class.forName("com.mysql.jdbc.Driver");
            for(int i=0;i<3;i++){
                Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test1", "root", "root");
                pool.add(connection);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 獲取連線
     * @return
     */
    public static Connection getConnection(){
        try {
            if(!pool.isEmpty()){
                Connection connection = pool.removeFirst();
                return connection;
            }
            //如果沒有連線 等待 100 毫秒 然後繼續
            Thread.sleep(100);
            return getConnection();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 歸還連線 其實就是重新將連線新增到池子中去
     * @param connection
     */
    public static void release(Connection connection){
        if(connection != null){
            pool.add(connection);
        }
    }
}
複製程式碼

0202 c3p0連線池使用

免費的連線池有很多,如果不配合 spring 。單獨使用,我個人還是喜歡使用 c3p0。

第一步 新增依賴

    <!-- https://mvnrepository.com/artifact/c3p0/c3p0 -->
    <dependency>
      <groupId>c3p0</groupId>
      <artifactId>c3p0</artifactId>
      <version>0.9.1.2</version>
    </dependency>
複製程式碼

第二步 配置檔案 配置檔案命名只能 c3p0-config.xml ,位置放在 idea-->src-->resources-->c3p0-config.xml 然後就是它的配置,有兩種情況

  • 預設不設定名字
  • 設定名字 首先來看預設不設定名字,比較簡單,配置比較簡單。程式碼中使用也可以直接使用。
	<!-- This is default config! -->
	<default-config>
		<property name="driverClass">com.mysql.jdbc.Driver</property>
		<property name="jdbcUrl">jdbc:mysql://localhost:3306/mylink2mv?characterEncoding=utf8</property>
		<property name="user">root</property>
		<property name="password">root</property>
		<property name="acquireIncrement">5</property>
		<property name="initialPoolSize">10</property>
		<property name="minPoolSize">5</property>
		<property name="maxPoolSize">20</property>
		<property name="maxStatements">0</property>
		<property name="maxStatementsPerConnection">5</property>
	</default-config>
複製程式碼

還有種方式就是設定配置名字,如下 test1

	<!-- This is my config for mysql-->
	<named-config name="test1">
		<property name="driverClass">com.mysql.jdbc.Driver</property>
		<property name="jdbcUrl">jdbc:mysql://localhost:3306/test1</property>
		<property name="user">root</property>
		<property name="password">root</property>
		<property name="initialPoolSize">10</property>
		<property name="maxIdleTime">30</property>
		<property name="maxPoolSize">100</property>
		<property name="minPoolSize">10</property>
		<property name="maxStatements">200</property>
	</named-config>
複製程式碼

剩下就是程式碼中的事情了。

第三步程式碼中初始化配置檔案

    //載入預設配置檔案
    //private static final ComboPooledDataSource DATA_SOURCE_DEFAULT =new ComboPooledDataSource();
    
    //載入命名配置檔案
    private static final ComboPooledDataSource DATA_SOURCE_TEST1 =new ComboPooledDataSource("test1");

複製程式碼

兩種載入方式,都比較簡單。稍微注意,就是這一行程式碼就已經讀取配置檔案了,不用做其他操作。這裡已經得到的就是連線池了。這裡我們寫個方法來獲取,顯得專業點,不然直接通過類名獲取也沒事。一般大部分舉例都是採用讀取預設名字的配置,這裡我讀取自定義的,將上面的程式碼註釋掉。

第四步 獲取連線

這裡我寫個簡單的從連線池獲取連線的方法。

    public static ComboPooledDataSource getDataSource(){
        return pooledDataSource;
    }

    public static Connection getCoonection(){
        try {
            Connection connection = getDataSource().getConnection();
            return connection;
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
複製程式碼

第五步 測試

這裡因為還沒開始用 DBUtils ,所以都是原生的 jdbc 方法。寫了個簡單的查詢語句演示。

    public static void main(String[] args) {
        try {
            Connection connection = C3P0Utils.getCoonection();
            PreparedStatement preparedStatement = null;
            ResultSet rs = null;
            String sql = "select * from my_movie;";
            preparedStatement = connection.prepareStatement(sql);
            rs = preparedStatement.executeQuery();
            while(rs.next()){
                String mName = rs.getString("mName");
                System.out.println(mName);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
複製程式碼

【很全很新】C3P0 連線池和 DBUtils 配合事務使用總結

0203 DBUtils

現在開始使用 DBUtils ,注意它並不是連線池 ,最開始學的時候兩者總是分不清,它是結合連線池 C3P0 使用的工具類。如上面程式碼,發現沒,就算不使用 DBUtils 也是可以的,只不過運算元據庫那一塊,增刪改查太過於複雜,特別對於「查」來說。對於返回的資料,處理很原生。但是有了 DBUtils 就不一樣了。它有三個核心功能,剛好用於解決專案實踐中很容易碰到的問題。

  • QueryRunner 中提供對 sql 語句操作的 api
  • ResultSetHandler 介面,用於定義 select 操作後,怎樣封裝結果集
  • 定義了關閉資源與事務處理的方法

如上所說,這三個核心功能都是解決「痛點」問題的。

**QueryRunner **

一個個來看。首先 QueryRunner 類的構造方法有好三個,但是常用的可以分成兩類

  • 帶 connection 的
  • 不帶 connection

首先來看不帶 connection 的,也就是說直接傳入連線池的。DBUtils 底層自動維護連線 connection 。

  QueryRunner queryRunner  = new QueryRunner(C3P0Utils.getDataSource());
複製程式碼

這種是大家用的最多的,還有種就是預設的不帶引數。

    QueryRunner queryRunner  = new QueryRunner();
複製程式碼

這兩種有什麼區別呢? 為什麼涉及 connection 呢? 主要還是事務!

這裡先將結果丟出來,如果涉及事務,不能使用第一種構造。

再來看 QueryRunner 的方法 api 。兩個運算元據庫的 api 。

  • update(String sql, Object ... params) ,用於執行 增刪改 sql 語句
  • query(String sql , ResutlSetHandler rsh , Object ... params) 執行 查詢 sql 語句。

**ResultSetHandler **

第二部分中的查詢語句中可以看出,對於查詢語句要解析到實體類,特別麻煩,特別結果集是集合時,更加麻煩。但是實踐中查詢用的最多。所以 DBUtils 有專門的類來處理結果集。非常多,但是常用三個。

  • 結果集是單 bean ----> BeanHanler
  • 結果集是集合 ---- BeanListHandler
  • 結果集是個單資料。(我一般專用來獲取 主鍵 id) ---- ScalarHandler

增刪改操作都比較簡單,比較特殊一點就是 **獲取插入成功後的主鍵值 **,這點後再說,先將簡單的寫完。

    /**
     * 新增
     */
    public void A() throws SQLException {
        QueryRunner queryRunner  = new QueryRunner(C3P0Utils.getDataSource());
        String sql = "insert into my_movie (mId,mName,mPrice,mDirector,mShowDate,cId) values (?,?,?,?,?,?)";
        Object params[] = {null, "邪惡力量", 20, "Haha", "2017-08-23", 2};
        int i = queryRunner.update(sql,params);
        System.out.println(i);
    }
複製程式碼
    /**
     * 刪除
     */
    public void B() throws SQLException {
        QueryRunner queryRunner  = new QueryRunner(C3P0Utils.getDataSource());
        String sql = "delete from my_movie where mId = ?";
        Object params[] = {7};
        int i = queryRunner.update(sql,params);
        System.out.println(i);
    }
複製程式碼
    /**
     * 改
     */
    public void C() throws SQLException {
        QueryRunner queryRunner  = new QueryRunner(C3P0Utils.getDataSource());
        String sql = "update my_movie set mName = ? where mId = ?";
        Object params[] = {"綠箭俠",1};
        int i = queryRunner.update(sql,params);
        System.out.println(i);
    }
複製程式碼

上面三個方法我都試過,執行是沒問題的。接下來就是 獲取插入成功後的主鍵值。因為我碰到過這個需求,結果搜尋 c3p0 dbutils 插入成功主鍵值 找到的相關文章都沒解決我的問題。查了很久 踩了很多坑,這裡總結下。

     /**
     * 獲取插入成功後的主鍵值
     */
   public void D() throws SQLException {
        QueryRunner queryRunner  = new QueryRunner(C3P0Utils.getDataSource());
        String sql = "insert into my_movie (mId,mName,mPrice,mDirector,mShowDate,cId) values (?,?,?,?,?,?)";
        Object params[] = {null, "邪惡力量", 20, "Haha", "2017-08-23", 2};
        //這裡不能使用 update 而是 insert 使用如下引數
        Long i = (Long) queryRunner.insert(sql,new ScalarHandler(),params);
        System.out.println(i);
    }
複製程式碼

接下來是查詢

    @Test
    /**
     * 查詢 獲取單個結果集
     */
    public void E() throws SQLException {
        QueryRunner queryRunner  = new QueryRunner(C3P0Utils.getDataSource());
        String sql = "select * from my_movie where mId  = ? ";
        Object params[] = {1};
        Movie movie = queryRunner.query(sql, new BeanHandler<Movie>(Movie.class), params);
        System.out.println(movie);
    }

    @Test
    /**
     * 查詢 獲取單個結果集
     */
    public void F() throws SQLException {
        QueryRunner queryRunner  = new QueryRunner(C3P0Utils.getDataSource());
        String sql = "select * from my_movie ";
        Object params[] = {};
        List<Movie> movieList = queryRunner.query(sql, new BeanListHandler<Movie>(Movie.class), params);
        System.out.println(movieList.toString());
    }
複製程式碼

0204 配合事務

到這裡為止,一切都很美好,沒啥問題。但是如果涉及 事務 呢。

如果一個方法中不止一個 sql 操作,而都在一個事務中。採用如上辦法是不行的。因為這些 sql 語句的操作前提需要保證使用的是同一個連線

但是使用 QueryRunner 如上的構造方法是不行的。每一個操作queryRunner.query可能都使用的不是同一個連線。所以我們的做法是自己獲取連線,然後作為引數傳入。

    /**
     * 事務
     */
    @Test
    public void G() {
        Connection connection = null;
        try {
            QueryRunner queryRunner  = new QueryRunner();
            connection = C3P0Utils.getCoonection();

            //開啟事務
            connection.setAutoCommit(false);

            //所有的操作都帶上引數 connection
            String sql01 = "insert into my_movie (mId,mName,mPrice,mDirector,mShowDate,cId) values (?,?,?,?,?,?)";
            Object params01[] = {null, "邪惡力量", 20, "Haha", "2017-08-23", 2};
            int i = queryRunner.update(connection,sql01,params01);

            String sql02 = "select * from my_movie ";
            Object params02[] = {};
            List<Movie> movieList = queryRunner.query(connection,sql02, new BeanListHandler<Movie>(Movie.class), params02);

            System.out.println(movieList);
        } catch (SQLException e) {

            //如果報錯 回滾
            try {
                connection.rollback();
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
            e.printStackTrace();
        }finally {
            try {
                //不管成功 失敗 都提交
                connection.commit();
                //關閉連線
                DbUtils.closeQuietly(connection);
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
複製程式碼

上面程式碼我只是舉例了插入和查詢。其他都一樣,帶了 connection 引數即可。

03 尾聲

基本 c3p0 dbutils 事務 所有問題都搞清楚了。以後可能也用不著這些框架,但是希望能幫到一些用到了朋友吧。

相關文章