1 再談事務
一個事務本質在於:其內部的功能要麼都成功,要麼都失敗!
之前的blog中談過事務,那一篇是在Sequel Pro中測試的,此處重點談論如何用Idea來操作事務!
ACID原則
原子性:要麼全部完成,要麼都不完成
一致性:總數不變
隔離性:多個進程互不干擾
持久性:一旦提交不可逆,持久化到數據庫內
隔離性的問題:
髒讀:一個事務讀取了另一個沒有提交的事務
不可重複讀:在同一個事務內,重複讀取表中的數據,數據發生了改變
幻讀(虛讀):在一個事務內,讀取到了別人插入的數據,導致前後讀出來的結果不一致。
代碼實現
- 開啓事務
conn.setAutoCommit(false);
- 一組業務執行完畢,提交事務!
- 可以在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
同樣需要導入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&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&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!