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

好久沒用原生鏈接池,最近想寫個小功能,結果發現不少地方不太懂,而後網上搜了半天的 c3p0 相關內容,全不符合我想要的。相同內容太多 並且沒什麼,因此我本身來總結下吧。mysql

01 總結全文

從如下來總結spring

  • 鏈接池的做用,爲何要使用鏈接池
  • 書寫本身的鏈接池,用於理解框架 c3p0 等鏈接池
  • 鏈接池框架 c3p0 使用
  • 鏈接池框架 c3p0 和 DBUtils 的配合使用
  • 配合事務的使用(重點,這塊不少人都說不明白)

02 分析

0201 鏈接池的做用,爲何要使用鏈接池

首先咱們操做數據庫都須要鏈接,平時獲取鏈接、關閉鏈接若是頻繁,就會浪費資源,佔用CPU。因此這裏咱們用一個池子來存放鏈接。 先自定義一個本身的鏈接池,這些內容太簡單,我直接上代碼。相信你們很容易看懂。sql

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。數據庫

第一步 添加依賴api

<!-- 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 而後就是它的配置,有兩種狀況bash

  • 默認不設定名字
  • 設定名字 首先來看默認不設定名字,比較簡單,配置比較簡單。代碼中使用也能夠直接使用。
<!-- 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>
複製代碼

剩下就是代碼中的事情了。ide

第三步代碼中初始化配置文件工具

//加載默認配置文件
    //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();
        }
    }
複製代碼

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 事務 全部問題都搞清楚了。之後可能也用不着這些框架,可是但願能幫到一些用到了朋友吧。

相關文章
相關標籤/搜索