1.數據庫事務特性
我們都知道mysql數據庫的事務四大特性ACID
2.臟讀、幻讀、不可重復讀
不可重復讀:?事務1對數據讀取后,事務2對其數據進行修改,事務1再次讀取數據時,得到和第一次讀取不同的數據。
幻讀?事務1對某數據讀取后,事務2對其數據進行刪除或者添加記錄,當事務1再次讀取時發現記錄數減少或者增加了
以下是各個隔離級別所對臟讀、幻讀、不可重復讀的支持情況,默認下mysql的隔離級別是可重復讀。
4. 未提交讀產生臟讀演示案例
我們在連接1中,設置當前會話事務隔離級為未提交讀,之后開啟事務向數據庫里面插入了一條記錄,并不手動執行 語句,此時事務處于未提交狀態:
此時在連接2中設置隔離就級別為未提交讀,并查詢數據表,此時結果為:
此時可以看到連接2是可以查詢到連接1為提交事務的數據,所有此時就會產生臟讀
4. 已提交讀會不會產生臟讀?
我們將數據庫的隔離級別修改為已提交讀,并開啟事務,向表中插入一條數據,不執行:
此時我們再打開連接2,查詢:
此時我們可以看到連接2是讀取不到連接1未提交的數據的。
小結:mysql提供了4種隔離級別網站數據庫創建失敗,每個級別對臟讀、不可重復讀、幻讀 這三者提供了不同的解決,這4種隔離級別自上而下(上表中),能解決問題的能力越強。在上表中最安全的級別則是 可串行化,但是mysql為什么默認的隔離級別不是它呢,原因是 因為隔離級別越高,其并發性能越低。
5. JDBC事務控制
JDBC 是 Java 語言中用來規范客戶端程序如何來訪問數據庫的應用程序接口,提供了查詢和更新數據庫中數據的方法。JDBC 也是 Sun 的商標(現在屬于 ),是面向關系型數據庫的。
最原始的JDBC數據庫連接方式,如下所示:
public?void?updateCoffeeSales()throws?SQLException {
????try?{
????????//獲取連接
????????Connection conn = DriverManager.getConnection(DB_URL,USER,PASS);
????????//關閉自動提交
????????con.setAutoCommit(false);
????????//DML數據庫操作
????????...
????????//手動提交事務
?????????con.commit();
?????????
????????} catch?(SQLException e ) {
????????????//出現異常回滾
????????????con.rollback();
???????
????????} finally?{
????????//關閉連接等操作
????}
}
在本段代碼塊中,在JDBC處理事務時我們需要手動的進行事務提交,當發生異常時調用連接對象的回滾方法進行回滾操作。在此也可見事務最基本的依賴是數據庫連接對象,
6.的事務支持6.1 事務管理器
并不自己實現事務的管理,而是給出來一個抽象的事務管理接口或者超類,需要其他db框架來具體實現。提供的抽象事務管理器接口:
// Spring提供的事務管理器頂級接口
public?interface?TransactionManager?{
}
--------------
public?interface?PlatformTransactionManager?extends?TransactionManager?{
????????// 根據事務定義獲取事務狀態
?????TransactionStatus getTransaction(@Nullable TransactionDefinition definition)throws?TransactionException;
?????//提交事務
?????void?commit(TransactionStatus status)?throws?TransactionException;
?????//回滾事務
?????void?rollback(TransactionStatus status)?throws?TransactionException;
}
由以上兩張圖可以見得 管理器所在的包為-jdbc中,是一個第三方的DB框架。并不自己實現事務的管理,而是給出來一個抽象的事務管理接口或者超類,需要其他db框架來具體實現
6.2關鍵名詞解釋:
:這是提供的一個事務管理器的抽象接口,接口里面定義了基礎的事務方法。
public?interface?PlatformTransactionManager?extends?TransactionManager?{
????//返回當前活動的事務或創建一個新的事務
?TransactionStatus?getTransaction(@Nullable?TransactionDefinition definition)
???throws?TransactionException;
?//提交給定事務
?void?commit(TransactionStatus status) throws?TransactionException;
?//回滾指定事務
?void?rollback(TransactionStatus status) throws?TransactionException;
}
n:事務的定義,在里面定義了一些事務傳播行為和事務隔離級別如:
public?interface?TransactionDefinition?{
//------事務傳播行為------
//支持當前事務; 如果不存在,則創建一個新的。
int?PROPAGATION_REQUIRED = 0;
// 支持當前事務; 如果不存在,則以非事務方式執行。
int?PROPAGATION_SUPPORTS = 1;
//支持當前事務;如果沒有當前事務,拋出異常
int?PROPAGATION_MANDATORY = 2;
//創建一個新事務,掛起當前事務(如果存在)。
int?PROPAGATION_REQUIRES_NEW = 3;
//不支持當前事務;而是始終以非事務方式執行。
int?PROPAGATION_NOT_SUPPORTED = 4;
//不支持當前事務;如果當前事務,拋出異常
int?PROPAGATION_NEVER = 5;
//如果當前事務存在,則在嵌套事務中執行,
int?PROPAGATION_NESTED = 6;
//------事務隔離級別------
//使用底層數據存儲的默認隔離級別。
int?ISOLATION_DEFAULT = -1;
//未提交讀
int?ISOLATION_READ_UNCOMMITTED = 1;
//已提交讀
int?ISOLATION_READ_COMMITTED = 2;
//可重復讀
int?ISOLATION_REPEATABLE_READ = 4;
//串行化
int?ISOLATION_SERIALIZABLE = 8;
// 使用底層事務系統的默認超時
int?TIMEOUT_DEFAULT = -1;
// 在接口中提供默認的事務傳播行為方法
default?int?getPropagationBehavior() {
??return?PROPAGATION_REQUIRED;
?}
//默認事務隔離級別
default?int?getIsolationLevel() {
??return?ISOLATION_DEFAULT;
?}
}
:事務保存結點,當一個方法中有多個數據庫修改操作,發生異常時我們并不想回滾全部操作,而只想回滾到某個節點,此時我們需要手動創建回滾的節點,這個接口中提供了三個方法(具體實現是DB框架)如下:
public?interface?SavepointManager?{
????//創建一個新的保存點。可以回滾到特定的保存點
?Object createSavepoint()?throws?TransactionException;
?//回滾到給定的保存點。
?void?rollbackToSavepoint(Object savepoint)?throws?TransactionException;
?//顯式釋放給定的保存點。
?void?releaseSavepoint(Object savepoint)?throws?TransactionException;
?}
:事務當前狀態的通用表示。
public?interface?TransactionExecution?{
??//返回當前事務是否為新事務;
?boolean?isNewTransaction();
??//設置事務僅回滾
?void?setRollbackOnly();
?//返回事務是否已標記為僅回滾
?boolean?isRollbackOnly();
????//返回該事務是否完成,
?boolean?isCompleted();
}
:事務狀態表示
public?interface?TransactionStatus?extends?TransactionExecution, SavepointManager, Flushable?
????// 事務內部是否有保存結點
?boolean?hasSavepoint();
?// 刷新
?@Override
?void?flush();
}
6.2 編程式事務
中推薦使用接口來實現編程式事務:
@RequestMapping("test")
public?class?TestTx?{
????@Autowired
????private?TransactionTemplate transactionTemplate;
????@Autowired
????private?IResourceAdminService iResourceAdminService;
????@GetMapping()
????public?void?test(){
????????transactionTemplate.execute(new?TransactionCallbackWithoutResult() {
????????????@Override
????????????protected?void?doInTransactionWithoutResult(TransactionStatus status)?{
????????????????try?{
????????????????????ResourceAdmin resourceAdmin = new?ResourceAdmin();
????????????????????resourceAdmin.setName("張三");
????????????????????resourceAdmin.setPassword("123456");
????????????????????// 數據庫保存操作
????????????????????boolean?save = iResourceAdminService.save(resourceAdmin);
????????????????????if(save){
????????????????????????log.info("數據庫保存1成功");
????????????????????}
????????????????????// 模擬異常
????????????????????int?i=5/0;
????????????????}catch?(Exception e){
????????????????????e.printStackTrace();
????????????????????status.setRollbackOnly();
????????????????????log.error("發生異常回滾事務");
????????????????}
????????????}
????????});
????}
}
6.3 淺淡源碼中提供的方法:
@Override
?@Nullable
?public? T execute(TransactionCallback action) ?throws?TransactionException {
??Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");
??if?(this.transactionManager instanceof?CallbackPreferringPlatformTransactionManager) {
???return?((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
??}
??else?{
??????// 1.根據當前事務管理器獲取事務狀態
???TransactionStatus status = this.transactionManager.getTransaction(this);
???T result;
???try?{
???//2.事務操作
????result = action.doInTransaction(status);
???}
???catch?(RuntimeException | Error ex) {
????// Transactional code threw application exception -> rollback
???//3.異常回滾
????rollbackOnException(status, ex);
????throw?ex;
???}
???catch?(Throwable ex) {
????// Transactional code threw unexpected exception -> rollback
????rollbackOnException(status, ex);
????throw?new?UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
???}
???//4.事務管理器提交事務
???this.transactionManager.commit(status);
???return?result;
??}
?}
方法的參數是一個函數式接口:
( )方法是我們需要重寫的
@FunctionalInterface
public?interface?TransactionCallback<T> {
????//當使用excute方法時需要是重寫這個方法
?@Nullable
?T doInTransaction(TransactionStatus status);
}
(,ex)方法實現:
private?void?rollbackOnException(TransactionStatus status, Throwable ex)?throws?TransactionException {
??Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");
??logger.debug("Initiating transaction rollback on application exception", ex);
??try?{
??????// 使用事務管理器會滾事務
???this.transactionManager.rollback(status);
??}
??catch?(TransactionSystemException ex2) {
???logger.error("Application exception overridden by rollback exception", ex);
???ex2.initApplicationException(ex);
???throw?ex2;
??}
??catch?(RuntimeException | Error ex2) {
???logger.error("Application exception overridden by rollback exception", ex);
???throw?ex2;
??}
?}
(,ex)方法原理也是調用了管理器的方法,進行事務回滾
根據事務結點創建回滾事務
在上文中我們提到這個接口,這個接口中三個方法網站數據庫創建失敗,其中有一個創建事務節點和回滾到指定事務結點的方法,下面例子將演示這個兩個方法的用法:
Object savepoint=null;
????????????try?{
????????????????ResourceAdmin resourceAdmin = new?ResourceAdmin();
????????????????resourceAdmin.setName("張三");
????????????????resourceAdmin.setPassword("123456");
????????????????// 數據庫保存操作
????????????????boolean save = iResourceAdminService.save(resourceAdmin);
????????????????if(save){
????????????????????log.info("數據庫保存1成功");
????????????????}
????????????????// 創建事務結點
????????????????savepoint = status.createSavepoint();
????????????????resourceAdmin.setName("李四");
????????????????// 數據庫保存操作
????????????????save = iResourceAdminService.save(resourceAdmin);
????????????????if(save){
????????????????log.info("數據庫保存2成功");
????????????????}
????????????????// 模擬異常
????????????????int?i=5/0;
????????????}catch?(Exception e){
????????????????e.printStackTrace();
????????????????log.error("發生異常回滾事務");
????????????????// 回滾到指定事務節點
????????????????status.rollbackToSavepoint(savepoint);
????????????}
在以上代碼塊中,我們保存張三用戶成功后,創建了一個事務節點,之后又保存了李四用戶,在異常處理模塊中回滾到保存李四之前,如果代碼運行成功那么數據庫中只會有存在張三用戶,運行測試:
通過上圖我們可以發現事務確實是回滾到了保存李四用戶之前的。
補充:其實在以上例子中我們重寫方法時,如果在方法內捕獲異常時我們不自己調用.()方法回滾事務,模板方法也是會幫我們進行事務回滾和提交的。如下圖:
我們的事務操作代碼其實是放在.();中運行的,只是把自己的數據庫操作語句嵌入到該方法中,所以如果我們調用方法時忘記進行回滾操作,這個模板方法也是會自動幫我們進行事務的提交和回滾,所以不會造成阻塞等問題。