事務Transaction
Transaction,台灣:交易,中國:事務
-
在MySQL中只有使用了Innodb資料庫引擎的資料庫或表才支援事務
-
事務 = 一組操作資料庫的動作集合,使資料從一種狀態變換為另一種狀態
-
一個事務保證一組SQL語句要嘛全部執行,要嘛全部不執行
- 要嘛提交Commit,要嘛回滾Rollback
-
數據一旦提交,就不可回滾
-
那些操作會自動提交?
- DDL(刪庫建表之類的操作):必定自動提交
- DML(操作某筆資料):預設會自動提交,但可以
set autocommit = false
- 關閉連接時,預設也會自動提交
事務的相關方法
由
java.sql.Connection
包提供,顯然這些方法都是由Connection物件調用
public void setAutoCommit(boolean)
public boolean getAutoCommit()
public void commit()
public void rollback()
事務實作流程
- 關閉自動提交
- 執行事務語句,可能有多行
commit()
手動提交,並還原自動提交設定- 於
catch (Exception e)
區塊使用rollback()
回滾
// 範例
public String delete(String id) {
String ID = id;
db = new getConnection();
Connection con = db.getConnection();
try {
con.setAutoCommit(false);
db.executeUpdate("delete from helloworld where ID=" + ID); //更新操作1
db.executeUpdate("delete from helloworld _book where ID=" + ID); //更新操作2
db.executeUpdate("delete from helloworld_user where ID=" + ID); //更新操作3
con.commit();//提交JDBC事務
con.setAutoCommit(true);
db.close();
return "success";
}
catch (Exception e) {
con.rollBack();//回滾JDBC事務
e.printStackTrace();
db.close();
return "fail";
}
}
事務的ACID
- 原子性(Atomicity):一個事務(transaction)中的所有操作不可分割,要麼全部完成,要麼全部不完成,不會結束在中間某個環節。事務在執行過程中發生錯誤,會被回滾(Rollback)到事務開始前的狀態,就像這個事務從來沒有執行過一樣
- 一致性(Consistency):在事務開始之前和事務結束以後,資料庫的完整性沒有被破壞。這表示寫入的資料必須完全符合所有的預設規則,這包含資料的精確度、串聯性以及後續資料庫可以自發性地完成預定的工作。
- 隔離性(Isolation):資料庫允許多個併發事務同時對其資料進行讀寫和修改的能力,隔離性可以防止多個事務併發執行時由於交叉執行而導致資料干擾。事務隔離分為不同級別,下述
- 持久性性(Durability):交易處理結束後,對資料的修改就是永久的,即便系統故障也不會丟失。
隔離性
資料庫的併發問題
- 髒讀(dirty read):A讀取了已經被B更新但未提交的資料,之後B回滾,導致A讀取了無效的"髒"資料
- 不可重複讀(non-repeatable read):A與B讀取了資料,之後B更新了資料,導致A再讀取時前後資料不同
- 幻讀(phantom read):A從一張表讀取了某些資料,B在該表插入了一些新的行,導致A再讀取表時前後資料量不同
隔離等級
隔離等級由低至高,越下面越安全,效率也越低
- 讀未提交(Read uncommitted):允許讀取其他事務未提交的變更,3種問題都會有
- 讀已提交(Read committed):只允許讀取其他事務已提交的變更,可避免髒讀
- 可重複讀(Repeatable read):確保一個事務多次讀取一個字段都是相同的值,在此事務持續時間內,禁止其他事務對這個字段進行更新,可避免髒讀與不可重複讀
- 序列化(Serializable):確保一個事務從一個表中讀取到相同的行,在此事務持續時間內,禁止其他事務對整張表進行插入、更新與刪除,可避免所有問題,但效率很低
資料庫差異
- Oracle支援2種事務隔離級別:Read committed與Serializable
- Oracle預設的事務隔離級別為:Read committed
- MySQL支援 4 種事務隔離級別
- MySQL預設的事務隔離級別為:Repeatable read
MySQL設定隔離
-
每啟動一個mysql程序,就會獲得一個單獨的資料庫連接,每個資料庫連接都有一個全域變數
@@tx_isolation
表示當前的事務隔離級別- mysql8為
@@transaction_isolation
- mysql8為
-
查看當前的隔離級別:
SELECT @@tx_isolation;
- 設置當前MySQL連接的隔離級別:
set transaction isolation level read committed;
- 設置資料庫系統的全域的隔離級別:
set global transaction isolation level read committed;
- 補充操作,創建MySQL資料庫使用者
create user tom identified by 'abc123';
- 授予許可權
#授予通過網路方式登錄的tom使用者,對所有庫所有表的全部許可權,密碼設為abc123.
grant all privileges on *.* to tom@'%' identified by 'abc123';
#給tom使用者使用本地命令列方式,授予atguigudb這個庫下的所有表的插刪改查的許可權。
grant select,insert,delete,update on atguigudb.* to tom@localhost identified by 'abc123';
DAO
https://www.bilibili.com/video/BV1eJ411c7rf?p=45
他這邊講這個相當於自己造輪子,以後框架用到的時候矇了可以再回來看
DataAccessObjects
資料存取物件,專門用來跟資料庫打交道,位於業務邏輯與資料之間的橋樑,確切來說就是把獲取/關閉連接與增刪改查封裝起來
- DAO介面:把對資料庫的所有操作定義成抽象方法,可以提供多種實現
- DAO 實現類:針對不同資料庫給出DAO介面定義方法的具體實現
- 實體類:用於存放與傳輸物件資料,比如表示查詢到的所有欄位存放的的物件
- 資料庫連接和關閉工具類:避免了資料庫連接和關閉代碼的重複使用,方便修改
同場加映JavaBean
JavaBean是一種public class
,目的是為了方便在程式中被重複使用,類似容器的概念,我不在乎你裡面封裝了啥,只要滿足特定條件大家都可以很方便地拿來用。JavaBean需滿足這4點約定:
- 所有屬性為private
- 提供預設空參的構造方法
- 提供public的getter和setter
- 實現serializable介面,可序列化保證可以跨平台溝通
連接池
即為資料庫連接建立緩衝池
- 想像有一口井,以前誰要用水就自己去打一桶水,多少人取水就要多少條連接
- 連接池就像在井口邊放一個大水缸,雇一位專業打水人負責井中取水事務,當其他人要用水只需要從水缸取即可,方便又快速
優勢
- 資源復用:不需要頻繁創立新的連接
- 提高速度:由連接池完成連接資料庫的初始化,誰要用只要來連池子,省時
- 資源分配:由連接池管理,可以限制最大連接數量等等
- 防止洩漏:其實也是管理,比如有人用到超時就把它收回
實現
-
JDBC的資料庫連接池使用
javax.sql.DataSource
接口來表示,此接口通常由WebServer實現,也有許多開源的例如:proxool
:更新時間截止2008年。速度可以,穩定性稍差,高併發的情況下會出錯c3p0
:太古老,代碼及其複雜,不利於維護dbcp
:是 apache上的一個 java 連接池項目,也是 tomcat 使用的連接池元件BoneCP
:13年前最快的連接池項目。2013年後不再更新,心灰意冷druid
:是alibba出品的一個功能比較全面,且擴展性較好的資料庫連接池,比較方便對jdbc介面進行監控跟蹤等HikariCP
:光連接池,目前被SpringBoot2官方推薦使用的資料庫連接池。
-
目前2021年的時間點偏向用HikariCP或druid
-
DataSource通常被稱為資料來源,它包含連接池和連接池管理兩個部分,習慣上也經常把DataSource稱為連接池
-
DataSource用來取代DriverManager來獲取Connection,獲取速度快,同時可以大幅度提高資料庫存取速度
-
特別注意:
- 資料來源和資料庫連接不同,資料來源無需創建多個,它是產生資料庫連接的工廠,因此整個應用只需要一個資料來源即可
- 當資料庫訪問結束後,程式還是像以前一樣關閉資料庫連接
conn.close()
它並沒有關閉資料庫的物理連接,它僅僅把資料庫連接釋放,歸還給了資料庫連接池
使用
即使不同家提供的連接池,流程也都大同小異
-
下載驅動,導入jar包
- https://github.com/alibaba/druid,這裡用德魯伊示範
- 阿帕契也有一個項目叫
Apache Druid
,那是一個資料儲存分析系統,兩者差很遠別搞混了
-
編輯設定檔
xxx.properties
,設定資料庫網址、帳號密碼,連接數…等等 -
造流,導入
properties
-
用提供的靜態方法建立
DataSource
物件 -
從
DataSource
物件getConnection()
// 範例,使用druid連接池
Properties properties = new Properties();
properties.load(getClass().getClassLoader().getResourceAsStream("druid.properties"));
// 或是直接類名.class.getClassLoader()...
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
Connection conn = dataSource.getConnection();
System.out.println(conn);
- 路徑src下的設定檔
druid.properties
url=jdbc:mysql://localhost:3306/jdbc_learn?rewriteBatchedStatements=true
username=root
password=1234
driverClassName=com.mysql.cj.jdbc.Driver
initialSize=10
maxActive=20
maxWait=1000
filters=wall
Dbutils工具包
了解底層原理後,實際開發當然是拿別人造好的輪子來用
- https://commons.apache.org/proper/commons-dbutils/
- Apache提供的開源 JDBC工具類庫,它是對JDBC的簡單封裝,學習成本極低,能極大簡化jdbc編碼的工作量,同時也不會影響程式的性能,包含3個主要API:
- org.apache.commons.dbutils.QueryRunner
- org.apache.commons.dbutils.ResultSetHandler
- org.apache.commons.dbutils.DbUtils
QueryRunner
用於增刪改查
update
:可以是增刪改insert
:只能用於插入batch
:批次處理query
:查詢,返回ResultSet
,並且會自行管理用到的PreparedStatement
與ResultSet
資源
// 範例
@Test
public void t03() throws Exception {
// 連接池
Properties properties = new Properties();
properties.load(getClass().getClassLoader().getResourceAsStream("druid.properties"));
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
Connection conn = dataSource.getConnection();
// 使用空參構造器
String sql = "update customers set name = ? where id=?";
QueryRunner qr = new QueryRunner();
qr.update(conn, sql, "BBB", 2);
// 使用dataSource構造
QueryRunner qr2 = new QueryRunner(dataSource);
qr2.update("update customers set name = ? where id=?", "CCC", 3);
}
- 創建QueryRunner物件時,構造器有沒有帶連接會決定執行操作(例如update)時第一個參數要不要傳入連接,這個差異不光在於省一個要寫的參數
- 假如我要提交一個事務,想當然我需要在同一條連接完成,如果發生錯誤就可以整串回滾
ResultSetHandler
更好處理ResultSet的中轉類
查詢時會得到java.sql.ResultSet
類物件,我們需要提早造一個Handler
類去處理,可選擇的如下:
ArrayHandler
:把結果集中的第一行資料轉成物件陣列。ArrayListHandler
:把結果集中的每一行資料都轉成一個陣列,再存放到List中。
BeanHandler
:將結果集中的第一行資料封裝到一個對應的JavaBean實例中。BeanListHandler
:將結果集中的每一行資料都封裝到一個對應的JavaBean實例中,存放到List裡。
MapHandler
:將結果集中的第一行資料封裝到一個Map裡,key是列名,value就是對應的值。MapListHandler
:將結果集中的每一行資料都封裝到一個Map裡,然後再存放到List
ColumnListHandler
:將結果集中某一列的資料存放到List中。KeyedHandler(name)
:將結果集中的每一行資料都封裝到一個Map裡,再把這些map再存到一個map裡,其key為指定的key。ScalarHandler
:查詢單個值對象
// 一樣先造一個QueryRunner
QueryRunner qr2 = new QueryRunner(dataSource);
// 造一個用來接收的Handler,類來自Customers的javabean
// 注意new的右邊()需要填入XXX.class
BeanHandler<Customers> cbh = new BeanHandler<>(Customers.class);
Customers queryCus = qr2.query("select * from customers where id = ?", cbh, 2);
System.out.println(queryCus);
// 接收N筆資料
BeanListHandler<Customers> cusListHandler = new BeanListHandler<>(Customers.class);
List<Customers> cusList = qr2.query("select * from customers", cusListHandler);
cusList.forEach(System.out::println);
// 將查來的一個結果直接裝成一個map
MapHandler mapHandler = new MapHandler();
Map<String, Object> objectMap = qr2.query("select * from customers where id = ?", mapHandler, 6);
System.out.println(objectMap);
// 將查來的多告結果裝成一個mapList ***重點用法***
MapListHandler mapListHandler = new MapListHandler();
List<Map<String, Object>> queryList = qr2.query("select * from customers where id <= ?", mapListHandler, 10);
queryList.forEach(System.out::println);
// scalarHandler查詢一個值
ScalarHandler scalarHandler = new ScalarHandler();
long count = (long) qr2.query("select count(*) from customers", scalarHandler);
System.out.println(count);
DbUtils
用於管理連接本身,所有方法都是靜態,最常就是用來關閉連接
public static void close(…) throws java.sql.SQLException
:省去檢查null,如果不是的話,它們就關閉Connection、Statement和ResultSetpublic static void closeQuietly(…)
:省掉檢查null跟拋異常public static void commitAndClose(Connection conn)throws SQLException
: 用來提交連接的事務,然後關閉連接public static void commitAndCloseQuietly(Connection conn)
public static void rollback(Connection conn)throws SQLException
:允許conn為null,因為方法內部做了判斷public static void rollbackAndClose(Connection conn)throws SQLException
rollbackAndCloseQuietly(Connection)
public static boolean loadDriver(java.lang.String driverClassName)
:裝載並註冊JDBC驅動程式,如果成功就返回true。使用該方法,你不需要捕捉這個異常ClassNotFoundException
小結
- 事務:關閉自動提交、catch區塊宣告回滾、都正常執行後再手動提交
- 連接池:導入驅動、造properties設定檔填入連接要素、造DataSource、獲取連接
- DbUtils:造QueryRunner用於CRUD,造ResultSetHandler處理查來的結果,用closeQuietly()關閉連接
上次修改於 2021-12-30