spring的事務操作(重點)

来源:https://www.cnblogs.com/yunjiandubu/archive/2018/12/23/10165404.html
-Advertisement-
Play Games

這篇文章一起來回顧複習下spring的事務操作.事務是spring的重點, 也是面試的必問知識點之一. 說來這次面試期間,也問到了我,由於平時用到的比較少,也沒有關註過這一塊的東西,所以回答的不是特別好,所以借這一篇文章來回顧總結一下,有需要的朋友,也可以點贊收藏一下,複習一下這方面的知識,為年後的 ...


這篇文章一起來回顧複習下spring的事務操作.事務是spring的重點, 也是面試的必問知識點之一.
說來這次面試期間,也問到了我,由於平時用到的比較少,也沒有關註過這一塊的東西,所以回答的不是特別好,所以借這一篇文章來回顧總結一下,有需要的朋友,也可以點贊收藏一下,複習一下這方面的知識,為年後的面試做準備.
首先,瞭解一下什麼是事務?
---
資料庫事務(Database Transaction) ,是指作為單個邏輯工作單元執行的一系列操作,要麼完全地執行,要麼完全地不執行。 事務處理可以確保除非事務性單元內的所有操作都成功完成,否則不會永久更新面向數據的資源。通過將一組相關操作組合為一個要麼全部成功要麼全部失敗的單元,可以簡化錯誤恢復並使應用程式更加可靠。一個邏輯工作單元要成為事務,必須滿足所謂的ACID(原子性、一致性、隔離性和持久性)屬性。事務是資料庫運行中的邏輯工作單位,由DBMS中的事務管理子系統負責事務的處理。這裡簡單提一下事務的四個基本屬性,

A(Atomic) 原子性

事務必須是原子工作單元;對於其[數據修改]事務必須是原子工作單元;對於其數據修改,要麼全都執行,要麼全都不執行.

C(Consistent) 一致性

事務在完成時,必須使所有的數據都保持一致狀態。在相關資料庫中,所有規則都必須應用於事務的修改,以保持所有數據的完整性。

I(Insulation) 隔離性

由併發事務所作的修改必須與任何其它併發事務所作的修改隔離。事務查看數據時數據所處的狀態,要麼是另一併發事務修改它之前的狀態,要麼是另一事務修改它之後的狀態,事務不會查看中間狀態的數據。

D(Duration) 一致性

事務完成之後,它對於系統的影響是永久性的。該修改即使出現致命的系統故障也將一直保持。
瞭解了事務之後,我們為什麼要使用事務呢?換句話說,用事務是為瞭解決什麼問題呢?

首先我們來看一個業務場景:Tom在書店買書,java和Oracle,2種書,單價都是100,庫存量都是10本,Tom目前身上有150元.現在Tom買1本書的錢是足夠的,ok,買起來,交易結束後,對於Tom來說,買到了1本書,還剩下50元.正好要出門時接到jack的電話,原來是jack要Tom幫他捎本java,他要用來複習,那接下來的交易是否可以正常進行呢?常識來說,50元買價值100元的東西肯定是買不到的,那我們看看程式中是什麼情況?

首先,需要構建三張表,餘額表,商品表,和商品庫存表,如下:

餘額表
商品表(書)
庫存表(書)

然後定義介面如下:

public interface BookShopDao {
    /**
     *   根據書名獲取書的單價
     */
    public  int findBookPriceByIsbn(String isbn);

    /**
     *   更新書的庫存,使書號對應的庫存-1
     */
    public  void  updateBookStock(String isbn);

    /**
     * 更新用戶的餘額:使username的balance-price
     * @param name
     * @param price
     */
    public  void updateUserAccount(String name,int price);

}
@Repository
public class BookShopDaoImpl implements  BookShopDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;


    @Override
    public int findBookPriceByIsbn(String isbn) {
        String sql = "SELECT  price FROM book WHERE isbn = ?";
        return jdbcTemplate.queryForObject(sql,Integer.class,isbn);
    }

    @Override
    public void updateBookStock(String isbn) {
        //檢查書的庫存是否足夠,不足夠則拋出異常
        String sql2 = "SELECT stock  FROM book_stock WHERE isbn = ?";
        int stock = jdbcTemplate.queryForObject(sql2, Integer.class, isbn);
        if(stock == 0){
            throw new BookStockException("庫存不足");
        }
        String sql ="UPDATE book_stock SET  stock= stock-1 where isbn = ?";
        jdbcTemplate.update(sql,isbn);

    }

    @Override
    public void updateUserAccount(String name, int price) {
        //驗證餘額是否足夠,不足則拋出異常
        String sql2 ="SELECT balance FROM account WHERE username = ?";
        int balance = jdbcTemplate.queryForObject(sql2, Integer.class, name);
        if(balance <price) {
            throw  new UserAccountException("餘額不足");
        }
        String sql = "UPDATE account SET balance = balance - ? WHERE  username = ?";
        jdbcTemplate.update(sql,price,name);

    }
}

上面代碼中有點要註意:庫存餘量是否充足,餘額是否充足,需要在代碼中去自己判斷,mysql不會幫我們加,例如,當庫存數為0時,如果仍需要減1,值會變為-1,這不是我們想要的結果.
接下里定義一個service:

public interface BookShopService {
    /**
     * 購物方法
     * @param username
     * @param isbn
     */
    public void  purchase(String username,String isbn);
}
@Service
public class BookShopServiceImpl implements BookShopService{
    @Autowired
    private BookShopDao shopDao;
    /**
     * @param username
     * @param isbn
     */
    @Override
    public void purchase(String username, String isbn) {
        //1.獲取書的單價
        int price = shopDao.findBookPriceByIsbn(isbn);
        //更新書的庫存
        shopDao.updateBookStock(isbn);
        //更新餘額
        shopDao.updateUserAccount(username,price);
    }
}

到此,基本購買流程都已經實現,我們來寫一個測試方法測試一下購買的結果是什麼?

餘額不足
測試結果

由圖中可以看出,程式報了"餘額不足"的異常,tom的餘額沒有減少,但是書店的庫存量卻減少了,這明顯是違反常理的,書店不會白白把書送給tom的,怎麼辦呢?事務就可以幫助我們解決這個難題.
這裡要先瞭解下事務的分類:

  • 編程式事務

    將事務管理代碼嵌入到業務方法中來控制事務的提交和回滾,在編程式管理事務當中,必須在每個事務操作中包含額外的事務管理代碼,繁瑣,不便.

  • 聲明式事務

    是建立在AOP之上的。其本質是對方法前後進行攔截,然後在目標方法開始之前創建或者加入一個事務,在執行完目標方法之後根據執行情況提交或者回滾事務。聲明式事務最大的優點就是不需要通過編程的方式管理事務,這樣就不需要在業務邏輯代碼中摻雜事務管理的代碼,只需通過基於@Transactional註解的方式或者配置文件中做相關的事務規則聲明,便可以將事務規則應用到業務邏輯中。

採用聲明式事務,基於@Transactional註解,首先看下配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
    <!--掃描包-->
    <context:component-scan base-package="com.springtest"></context:component-scan>
    <!--導入資源文件-->
    <context:property-placeholder location="classpath:db.properties" />
    <!--配置數據源-->
    <bean id="jdbcSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="user" value="${jdbc.user}"></property>
        <property name="password" value="${jdbc.password}"></property>
        <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
        <property name="driverClass" value="${jdbc.driverClass}"></property>
        <property name="initialPoolSize" value="${jdbc.initialPoolSize}"></property>
        <property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property>
    </bean>
    <!--配置spring的jdbctemplate模版-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="jdbcSource"></property>
    </bean>
    <!--配置事務管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="jdbcSource"></property>
    </bean>
    <!--啟用事務註解-->
    <tx:annotation-driven transaction-manager="transactionManager" />
</beans>

接下來給方法purchase()加上註解

    @Transactional()
    @Override
    public void purchase(String username, String isbn) {
        //1.獲取書的單價
        int price = shopDao.findBookPriceByIsbn(isbn);
        //更新書的庫存
        shopDao.updateBookStock(isbn);
        //更新餘額
        shopDao.updateUserAccount(username,price);
    }

結果如下:

測試-1
測試-2

由圖觀之,異常出現之後,事務發生了回滾,庫存不再減少,錢也不會再減少,結果正常.
拓展問題(面試):Q1: 假如此時BookShopServiceImpl中另外一個方法調用了purchase方法,那麼在另外一個方法中,事務是否起作用呢?
Q2:假如此時另外一個類中方法調用了BookShopServiceImpl類中的purchase方法,那麼事務又是否起作用呢?
我們來一一驗證一下,首先Q1

@Service
public class BookShopServiceImpl implements BookShopService{
    @Autowired
    private BookShopDao shopDao;

    @Transactional()
    @Override
    public void purchase(String username, String isbn) {
        //1.獲取書的單價
        int price = shopDao.findBookPriceByIsbn(isbn);
        //更新書的庫存
        shopDao.updateBookStock(isbn);
        //更新餘額
        shopDao.updateUserAccount(username,price);
    }

    @Override
    public void purchaseAgain(String username, String isbn) {
        purchase(username,isbn);
    }
}

測試結果:
測試前,資料庫數據為:

測試前
測試後
異常

結果觀之,事務並沒有起作用,原因是什麼?
啟用事務首先調用的是AOP代理對象而不是目標對象,首先執行事務切麵,事務切麵內部通過TransactionInterceptor環繞增強進行事務的增強,即進入目標方法之前開啟事務,退出目標方法時提交/回滾事務.而類內部的自我調用將無法實施切麵中的增強.,解決方案的話限於篇幅,以後再寫,這裡知道原因就可以了.
接下來驗證Q2,首先創建一個新的介面和實現類,裡面調用BookShopService 的purchase方法,觀察結果

@Service
public class TestBookShopServiceImpl implements TestBookShopService {
    @Autowired
    private BookShopService shopService;
    @Override
    public void testBookPurchase(String name, String isbn) {
        shopService.purchase(name,isbn);
    }
}

測試結果1
測試結果2

觀察結果,在餘額不足的情況下,外部方法調用purchase方法,拋出異常時,事務回滾,庫存沒有減少,原因同Q1相同,但正好相反,但是走了AOP代理,所以事務起作用了.


那麼如果在內部的方法purchaseAgain,和外部的方法中加入事務控制又會是怎樣的情況呢?
這裡直接給出結論:
purchaseAgain方法加入註解@Transactional後,調用purchase方法(無論是否添加@Transactional),事務控制起作用;外部類的testBookPurchase方法調用本類的purchase方法,事務控制也是起作用的.
由此引入spring關於事務的傳播行為的介紹:spring的事務傳播行為一共分為以下幾種:

  1. REQUIRED(常用)
  2. REQUIRES_NEW(常用)
  3. SUPPORTS
  4. NOT_SUPPORTED
  5. NEVER
  6. NESTED
  7. MANDATORY
    在@Transactional註解中是propagation屬性;

    事務傳播屬性

分別介紹:
PROPAGATION_REQUIRED 如果存在一個事務,則支持當前事務。如果沒有事務則開啟一個新的事務。(是spring 的預設事務傳播行為)。
PROPAGATION_REQUIRES_NEW 總是開啟一個新的事務。如果一個事務已經存在,則將這個存在的事務掛起。
PROPAGATION_SUPPORTS 如果存在一個事務,支持當前事務。如果沒有事務,則非事務的執行。但是對於事務同步的事務管理器,PROPAGATION_SUPPORTS與不使用事務有少許不同。
PROPAGATION_NOT_SUPPORTED 總是非事務地執行,並掛起任何存在的事務。
PROPAGATION_MANDATORY 如果已經存在一個事務,支持當前事務。如果沒有一個活動的事務,則拋出異常。
PROPAGATION_NEVER 總是非事務地執行,如果存在一個活動事務,則拋出異常。
PROPAGATION_NESTED 如果一個活動的事務存在,則運行在一個嵌套的事務中. 如果沒有活動事務, 則按TransactionDefinition.PROPAGATION_REQUIRED 屬性執行。


事務的傳播行為定義了事務的控制範圍,那麼事務的隔離級別定義的則是事務在資料庫讀寫方面的控制範圍.
有的時候,在程式併發的情況下,會發生以下的神奇情況:

  • 臟讀:對於兩個事務T1,T2,T1讀取了T2更新但是還未提交的欄位,之後,若T2回滾,那麼T1讀取的內容就是臨時且無效的
  • 不可重覆讀:對於兩個事務T1,T2, T1讀取了一個欄位,然後被T2更新了,之後T1再次讀取,欄位值變掉了.
  • 幻讀:兩個事務T1,T2, T1從一個表中讀取了一個欄位,然後T2在該表中插入了一些新的行,之後,如果T1再次讀取同一個表,就會多出幾行數據.
    那麼以上的問題要如何來解決呢,spring給出了它的解決方案,將事務的隔離性分為以下幾個等級
  • READ_UNCOMMITTED
  • READ_COMMITTED
  • REPEATABLE_READ
  • SERIALIZABLE
    在@Transactional註解中是propagation屬性;

    隔離級別

分別介紹:
READ_UNCOMMITTED 這是事務最低的隔離級別,它充許別外一個事務可以看到這個事務未提交的數據。 這種隔離級別會產生臟讀,不可重覆讀和幻像讀;
READ_COMMITTED 保證一個事務修改的數據提交後才能被另外一個事務讀取。另外一個事務不能讀取該事務未提交的數據。 這種隔離級別可以避免臟讀出現,但是可能會出現不可重覆讀和幻像讀;
REPEATABLE_READ 這種事務隔離級別可以防止臟讀,不可重覆讀。但是可能出現幻像讀;
SERIALIZABLE 這是花費最高代價但是最可靠的事務隔離級別。事務被處理為順序執行。 除了防止臟讀,不可重覆讀外,還避免了幻像讀;
以上幾種隔離界別, 在瞭解了其作用及其可避免的情況之後,我們在工作中視情況採用,不過一般預設情況就可以處理大多數情況了.
---
最後小結
這篇文章回顧了spring的事務相關的技術要點,包括什麼是事務,事務的四個基本屬性,為什麼要使用事務,事務的分類,事務的傳播種類以及事務的隔離級別.大體上涵蓋了事務的相關知識,但是並沒有深入到源碼級別來研究事務的相關實現,有機會一定要深入源碼瞭解實現,這樣才能對知識的學習理解達到庖丁解牛的地步,對自己以後的知識積累和提升也會有很大的幫助.


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 發生原因:運行javac編譯時沒有加上擴展名。解決方法:加上.java擴展名重新編譯即可,"xxxxxx.java"。 ...
  • IDE:Integrated Develop Environment,集成開發環境。利用IDE編程的好處不用多說,節省大量的時間和精力。 現在總結一下利用IDE編寫一個HelloWorld的具體流程。首先是建立一個項目,得有一個項目名。 具體就是:File->New->Java Project 接下 ...
  • 使用迭代器remove方法刪除元素 public class InteratorDemo { public static void main(String[] args) { List<String> list = new ArrayList<>(); list.add("老師"); list.ad ...
  • 在spring 3.2中,新增了@ControllerAdvice 註解,可以用於定義@ExceptionHandler、@InitBinder、@ModelAttribute,並應用到所有@RequestMapping中。@ControllerAdvice官方文檔。創建全局異常處理類:通過使用@C... ...
  • 簡介 本系列基於 的官方文檔,除去了文檔中一些冗餘的東西,加上了一些自己的理解,意圖是在於幫助更多初識 的人來進行一次探險。 本系列建議具有Java基礎和Spring使用經驗的同學學習。 什麼是Spring Boot 在使用之前,我們先來看看 到底是個啥,從字面意義上去理解,是Spring的一個引導 ...
  • 一、spring概述 Spring是一個開放源代碼的設計層面框架,他解決的是業務邏輯層和其他各層的松耦合問題,因此它將面向介面的編程思想貫穿整個系統應用。Spring是於2003 年興起的一個輕量級的Java 開發框架,由Rod Johnson創建。簡單來說,Spring是一個分層的JavaSE/E ...
  • 偶然看到一篇100多行實現SpringMvc的博客,閱讀後整理加實現出來。大家共勉!(紙上得來終覺淺,絕知此事要躬行。) 實現Spring的部分。 代碼解析如下: 1、首先創建Servelt,繼承HttpServlet。覆蓋init/doGet/doPost方法。 2、配置web.xml 2.1、配 ...
  • Python類練習 定義一個類 列印Point: Point實例化為一個p1: 我們直接列印 結果為 __main__.Point` 給實例p1添加屬性 (3, 4) 原點(0, 0)與p1之間的距離:5.0 (3, 4) 定義矩形 實例化一個例子r1 (100, 200) 列印矩形的寬度width ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...