MySQL Day11 再談事務與DBCP、C3P0數據庫連接池的配置

1 再談事務

一個事務本質在於:其內部的功能要麼都成功,要麼都失敗!

之前的blog中談過事務,那一篇是在Sequel Pro中測試的,此處重點談論如何用Idea來操作事務!

ACID原則

原子性:要麼全部完成,要麼都不完成

一致性:總數不變

隔離性:多個進程互不干擾

持久性:一旦提交不可逆,持久化到數據庫內

隔離性的問題:

髒讀:一個事務讀取了另一個沒有提交的事務

不可重複讀:在同一個事務內,重複讀取表中的數據,數據發生了改變

幻讀(虛讀):在一個事務內,讀取到了別人插入的數據,導致前後讀出來的結果不一致。

代碼實現

  1. 開啓事務 conn.setAutoCommit(false);
  2. 一組業務執行完畢,提交事務!
  3. 可以在catch語句中顯示的定義回滾語句,但默認失敗就會回滾

源碼:(其中用到上篇博客中的封裝類)

package com.demut.demo04;

import com.demut.demo02.utils.JdbcUtils;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class TestTransaction {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement st = null;
        ResultSet rs = null;

        try {
            conn = JdbcUtils.getConnection();

            conn.setAutoCommit(false); // 關閉自動提交-->開啓事務

            String sql1 = "update account set money = money-100 where name = 'A'";
            st = conn.prepareStatement(sql1);
            st.executeUpdate();

            //int x = 1/0; //可以故意設置報錯

            String sql2 = "update account set money = money+100 where name = 'B'";
            st = conn.prepareStatement(sql2);
            st.executeUpdate();

            //業務完畢,提交事務
            conn.commit();
            System.out.println("成功!");
        } catch (SQLException e) {
            try {
                conn.rollback(); //如果失敗則回滾事務
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
            e.printStackTrace();
        }finally {
            JdbcUtils.release(conn,st,rs);
        }
    }
}

2 數據庫連接池

爲什麼需要用到數據庫連接池呢?其原因在於我們每次連接數據庫都經歷了數據庫連接 --- 執行完畢 --- 釋放的過程,而其中的連接 --- 釋放 尤其浪費系統資源。

舉個例子:我們如果要去銀行辦理事務,倘若銀行採用來一個人 --- 開門 --- 辦理結束 --- 關門 --- 來一個人...的模式,十分耗費時間以及精力。所以我們開一次門,設置特定的業務員,使用業務員來對接每一位來的用戶,這樣就能很好的減少資源浪費,當然每位業務員也有自己的精力,所以有最大連接數,最小連接數,以及常用連接數!類似這樣的思路,我們引入數據庫的池化技術。

池化技術:準備一些預先的資源,過來就連接預先準備好的

--- 開門 --- 業務員: 等待 --- 服務 --

設置常用連接數:比如10個

設置最小連接數:比如10個

設置最大連接數:業務最高承載上限,例如15個

如果達到最大連接數,排隊等待,如果等待超時(例如100ms)就提示其異常。

其本質在於:編寫連接池,實現一個接口DataSource

開源數據源實現

現在的主流數據源有:DBCP、C3P0、Druid(阿里巴巴)

使用了這些數據庫連接池之後,我們在項目開發中就不需要編寫連接數據庫的代碼了!下面開始介紹使用方法:

2.1 DBCP

需要用到dbcp-jar包 :下載鏈接

pool-jar包:下載鏈接

如果導入後測試出現NoClassDefFoundError錯誤,則還需導入logging.jar包:下載鏈接

隨後將包導入到idea的lib目錄中,如果不知道如何導入,見我的另一篇博客:鏈接

另外需要添加一個配置文件:dbcp.properties,注意直接將該文件創建在src目錄下!

dbcp.properties文件代碼如下:

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbcStudy
username=root
password=123456

#<!-- 初始化連接 -->
initialSize=10

#最大連接數量
maxActive=50

#<!-- 最大空閒連接 -->
maxIdle=20

#<!-- 最小空閒連接 -->
minIdle=5

#<!-- 超時等待時間以毫秒爲單位 6000毫秒/1000等於60秒 -->
maxWait=60000

#<!-- 連接編碼設置 -->

connectionProperties=useUnicode=true;characterEncoding=utf8

#指定由連接池所創建的連接的自動提交(auto-commit)狀態。
defaultAutoCommit=true

編寫JdbcUtils_DBCP工具類:

package com.demut.demo05.utils;

import org.apache.commons.dbcp2.BasicDataSourceFactory;

import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;

public class JdbcUtils_DBCP {

    private static DataSource dataSource = null;
    //加載數據庫驅動
    static {
        try {
            //獲取文件流
            InputStream in = JdbcUtils_DBCP.class.getClassLoader().getResourceAsStream("dbcpconfig.properties");
            Properties properties = new Properties();
            properties.load(in); //加載流
            //創建數據源 工廠模式 --> 創建
            dataSource = BasicDataSourceFactory.createDataSource(properties);

        }catch (Exception e){
            e.printStackTrace();
        }

    }

    //獲取連接
    public static Connection getConnection() throws SQLException {
        return dataSource.getConnection(); //從數據源中獲取連接
    }

    //釋放連接資源
    public static void free(Connection conn, Statement st, ResultSet rs){
        if (rs!=null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (st !=null) {
            try {
                st.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (conn !=null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

另編寫測試類,測試使用DBCP連接:

package com.demut.demo05;

import com.demut.demo05.utils.JdbcUtils_DBCP;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Date;

public class TestDBCP {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement pst = null;
        ResultSet rs = null;
        try {
            conn = JdbcUtils_DBCP.getConnection();
            //區別
            //使用?佔位符代替參數
            String sql = "INSERT INTO users(id,`name`,`password`,`email`,`birthday`) VALUES (?,?,?,?,?)";
            pst = conn.prepareStatement(sql); //預編譯SQL,先寫sql,不執行

            //手動給參數賦值:
            pst.setInt(1,6); //此處的1表示第一個問好
            pst.setString(2,"Jever");
            pst.setString(3,"111111");
            pst.setString(4,"[email protected]");
            //注意: sql.Date 數據庫     java.sql.Date()
            //      util.Date  Java     new Date().getTime() 獲得時間戳
            pst.setDate(5,new java.sql.Date(new Date().getTime()));

            int i = pst.executeUpdate();
            if (i > 0) {
                System.out.println("插入成功!");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JdbcUtils_DBCP.free(conn,pst,rs);
        }
    }
}
/*測試結果:
插入成功!
*/

2.2 C3P0

需要下載的jar包:c3p0-x.x.x.x、mchange-commons-java-x.x.xx

提取碼:tyek

同樣需要導入jar包,同上!

創建配置文件c3p0-config.xml

<c3p0-config>
    <!--使用默認的配置讀取數據庫連接池對象 -->
    <default-config>
        <!--  連接參數 -->
        <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbcStudy?uesUnicode=true&amp;characterEncoding=utf8</property>
        <property name="user">root</property>
        <property name="password">123456</property>

        <!-- 連接池參數 -->
        <!--初始化申請的連接數量-->
        <property name = "acquireIncrement">5</property>
        <property name="initialPoolSize">10</property>
        <!--最小的連接數量-->
        <property name = "minPoolSize">5</property>
        <!--最大的連接數量-->
        <property name="maxPoolSize">20</property>
        <!--超時時間-->
        <property name="checkoutTimeout">3000</property>
    </default-config>

    <!--
    C3P0的命名配置
    -->
    <named-config name="MySQL">
        <!--  連接參數 -->
        <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbcStudy?uesUnicode=true&amp;characterEncoding=utf8</property>
        <property name="user">root</property>
        <property name="password">123456</property>

        <!-- 連接池參數 -->
        <!--初始化申請的連接數量-->
        <property name = "acquireIncrement">5</property>
        <property name="initialPoolSize">10</property>
        <!--最小的連接數量-->
        <property name = "minPoolSize">5</property>
        <!--最大的連接數量-->
        <property name="maxPoolSize">20</property>
        <!--超時時間-->
        <property name="checkoutTimeout">3000</property>
    </named-config>
</c3p0-config>

編寫JdbcUtils_C3P0工具類:

package com.demut.demo05.utils;

import com.mchange.v2.c3p0.ComboPooledDataSource;

import javax.sql.DataSource;
import javax.xml.crypto.Data;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;

public class JdbcUtils_C3P0 {
    private static DataSource dataSource = null;

    static {
        try {
            //創建數據源 工廠模式 --> 創建
            dataSource = new ComboPooledDataSource("MySQL"); //配置文件寫法
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    //獲取連接
    public static Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }

    //釋放連接資源
    public static void release(Connection conn, Statement st, ResultSet rs){
        if (rs!=null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (st !=null) {
            try {
                st.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (conn !=null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

另編寫測試類,測試使用C3P0連接:

package com.demut.demo05;

import com.demut.demo05.utils.JdbcUtils_C3P0;
import com.demut.demo05.utils.JdbcUtils_DBCP;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Date;

public class TestC3P0 {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement pst = null;
        ResultSet rs = null;
        try {
            conn = JdbcUtils_C3P0.getConnection(); //原來是自己實現的,現在是用別人實現的
            //區別
            //使用?佔位符代替參數
            String sql = "INSERT INTO users(id,`name`,`password`,`email`,`birthday`) VALUES (?,?,?,?,?)";
            pst = conn.prepareStatement(sql); //預編譯SQL,先寫sql,不執行

            //手動給參數賦值:
            pst.setInt(1,5); //此處的1表示第一個問好
            pst.setString(2,"Jever");
            pst.setString(3,"111111");
            pst.setString(4,"[email protected]");
            //注意: sql.Date 數據庫     java.sql.Date()
            //      util.Date  Java     new Date().getTime() 獲得時間戳
            pst.setDate(5,new java.sql.Date(new Date().getTime()));

            int i = pst.executeUpdate();
            if (i > 0) {
                System.out.println("插入成功!");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JdbcUtils_C3P0.release(conn,pst,rs);
        }
    }
}
/*運行結果:
四月 19, 2020 4:15:24 下午 com.mchange.v2.log.MLog <clinit>
信息: MLog clients using java 1.4+ standard logging.
四月 19, 2020 4:15:25 下午 com.mchange.v2.c3p0.C3P0Registry banner
信息: Initializing c3p0-0.9.2.1 [built 20-March-2013 10:47:27 +0000; debug? true; trace: 10]
四月 19, 2020 4:15:25 下午 com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource getPoolManager
信息: Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 5, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 3000, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, dataSourceName -> MySQL, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.cj.jdbc.Driver, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, identityToken -> 1hge0yxa914df8d71bm1ykq|2e5d6d97, idleConnectionTestPeriod -> 0, initialPoolSize -> 10, jdbcUrl -> jdbc:mysql://localhost:3306/jdbcStudy?uesUnicode=true&characterEncoding=utf8, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 0, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 20, maxStatements -> 0, maxStatementsPerConnection -> 0, minPoolSize -> 5, numHelperThreads -> 3, preferredTestQuery -> null, properties -> {user=******, password=******}, propertyCycle -> 0, statementCacheNumDeferredCloseThreads -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, userOverrides -> {}, usesTraditionalReflectiveProxies -> false ]
插入成功!

說明: 上部分爲日誌,可以自行修改,最後一行爲結果!
*/

Druid我們隨後再說!(此處挖個坑)

2.3 結論

無論使用什麼數據源,本質都是一樣的,DataSource接口不會變,方法就不會變!

寫在最後

Keep your servant also from willful sins; may they not rule over me. Then I will be blameless, innocent of great transgression. (Psalms 19 : 13)

To Demut!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章