day15-聲明式事務

来源:https://www.cnblogs.com/liyuelian/archive/2023/01/31/17081141.html
-Advertisement-
Play Games

聲明式事務 1.事務分類 編程式事務 Connection connection = JdbcUtils.getConnection(); try{ //1.先設置事務不要提交 connection.setAutoCommit(false); //2.進行業務 crud //3.提交事務 conne ...


聲明式事務

1.事務分類

  1. 編程式事務
Connection connection = JdbcUtils.getConnection();
try{
    //1.先設置事務不要提交
    connection.setAutoCommit(false);
    //2.進行業務 crud
    //3.提交事務
    connection.commit();
}catch(Exception e){
    //4.出現異常,回滾
    connection.rollback();
}
  1. 聲明式事務(後面以一個購買商品的系統為例)

2.聲明式事務-使用實例

2.1需求說明

  • 需求說明 - 用戶購買商品

    去處理用戶購買商品的業務邏輯:當一個用戶去購買商品,應該包含三個步驟:

    1. 通過商品 id 獲取價格
    2. 購買商品(某人購買商品,修改用戶餘額)
    3. 修改庫存量

    這裡一共涉及到三張表:用戶表、商品表、商品存量表。顯然,應該使用事務處理。

2.2解決方案分析

方案一:使用傳統的編程式事務來處理,將代碼寫到一起

(缺點是:代碼冗餘,效率低,不利於拓展;優點是簡單,好理解)

//例如:
Connection connection = JdbcUtils.getConnection();
try{
    //1.先設置事務不要提交
    connection.setAutoCommit(false);
    
    //2.進行業務 crud
    //多個表的修改,添加,刪除
    //select form 商品表 => 獲取價格
    //修改用戶餘額 update...
    //修改商品庫存量 update...
    
    //3.提交事務
    connection.commit();
}catch(Exception e){
    //4.出現異常,回滾
    connection.rollback();
}

方案二:使用 Spring 的聲明式事務來處理,可以將上面三個子步驟分別寫成一個方法,然後統一管理。

(這是Spring的優越性所在,開發中使用很多,優點是無代碼冗餘,效率高,拓展方便,缺點是理解較困難)底層使用AOP(動態代理+動態綁定+反射+註解)

2.3聲明式事務使用-代碼實現

  1. 創建表
-- 演示聲明式事務創建的表
-- 用戶表
CREATE TABLE `user_account`(
user_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
user_name VARCHAR(32) NOT NULL DEFAULT '',
money DOUBLE NOT NULL DEFAULT 0.0
)CHARSET=utf8;

INSERT INTO `user_account` VALUES(NULL,'張三', 1000);
INSERT INTO `user_account` VALUES(NULL,'李四', 2000);

-- 商品表
CREATE TABLE `goods`(
goods_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
goods_name VARCHAR(32) NOT NULL DEFAULT '',
price DOUBLE NOT NULL DEFAULT 0.0
)CHARSET=utf8 ;

INSERT INTO `goods` VALUES(NULL,'小風扇', 10.00);
INSERT INTO `goods` VALUES(NULL,'小臺燈', 12.00);
INSERT INTO `goods` VALUES(NULL,'可口可樂', 3.00);

-- 商品存量表
CREATE TABLE `goods_amount`(
goods_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
goods_num INT UNSIGNED DEFAULT 0
)CHARSET=utf8 ;

INSERT INTO `goods_amount` VALUES(1,200);
INSERT INTO `goods_amount` VALUES(2,20);
INSERT INTO `goods_amount` VALUES(3,15);

image-20230131192300386 image-20230131192318552 image-20230131192401446

  1. 創建GoodsDao
package com.li.tx.dao;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;

/**
 * @author 李
 * @version 1.0
 */
@Repository //將GoodsDao對象 註入到 spring 容器
public class GoodsDao {
    @Resource
    private JdbcTemplate jdbcTemplate;

    /**
     * 根據商品id,查詢對應的商品價格
     * @param id
     * @return
     */
    public Float queryPriceById(Integer id) {
        String sql = "select price from goods where goods_id = ?";
        Float price = jdbcTemplate.queryForObject(sql, Float.class, id);
        return price;
    }

    /**
     * 修改用戶餘額 [減少用戶餘額]
     * @param user_id
     * @param money
     */
    public void updateBalance(Integer user_id, Float money) {
        String sql = "update user_account set money=money-? where user_id=? ";
        jdbcTemplate.update(sql, money, user_id);
    }

    /**
     * 修改商品庫存量
     * @param goods_id
     * @param amount
     */
    public void updateAmount(Integer goods_id, int amount) {
        String sql = "update goods_amount set goods_num=goods_num-? where goods_id=? ";
        jdbcTemplate.update(sql, amount, goods_id);
    }
}
  1. 配置容器文件

因為使用了註解 @Resource 的方式自動裝配 JdbcTemplate 對象,這裡需要配置該對象。

<!--配置要掃描的包-->
<context:component-scan base-package="com.li.tx"/>

<!--引入外部的屬性文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>

<!--配置數據源對象-DataSource-->
<bean class="com.mchange.v2.c3p0.ComboPooledDataSource" id="dataSource">
    <!--給數據源對象配置屬性值-->
    <property name="user" value="${jdbc.user}"/>
    <property name="password" value="${jdbc.pwd}"/>
    <property name="driverClass" value="${jdbc.driver}"/>
    <property name="jdbcUrl" value="${jdbc.url}"/>
</bean>

<!--配置JdbcTemplate對象-->
<bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
    <!--給JdbcTemplate對象配置DataSource屬性-->
    <property name="dataSource" ref="dataSource"/>
</bean>
  1. 創建GoodsService,編寫方法,驗證不使用事務就會出現數據不一致現象
package com.li.tx.service;

import com.li.tx.dao.GoodsDao;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * @author 李
 * @version 1.0
 */
@Service //將GoodsService對象註入到容器中
public class GoodsService {
    @Resource
    private GoodsDao goodsDao;

    /**
     * 編寫一個方法,完成用戶購買商品的業務
     *
     * @param userId  用戶 id
     * @param goodsId 商品 id
     * @param amount  購買的商品數量
     */
    public void buyGoods(int userId, int goodsId, int amount) {
        //輸出購買的相關信息
        System.out.println("用戶購買信息 userId=" + userId
                + " goodsId=" + goodsId + " 購買數量=" + amount);

        //1.得到商品價格
        Float price = goodsDao.queryPriceById(goodsId);
        //2.減少用戶餘額
        goodsDao.updateBalance(userId, price * amount);
        //3.減少商品庫存量
        goodsDao.updateAmount(goodsId, amount);

        System.out.println("用戶購買成功...");
    }
}
  1. 新增添掃描的包
<context:component-scan base-package="com.li.tx.service"/>
  1. 為了測試,故意在Dao的sql語句中添加錯誤符號
image-20230131210013516

測試:

@Test
public void buyGoodsTest() {
    ApplicationContext ioc =
            new ClassPathXmlApplicationContext("tx.xml");
    GoodsService goodsService = ioc.getBean(GoodsService.class);
    goodsService.buyGoods(1,1,10);
}

測試結果:出現異常

image-20230131210119244

原始表信息:

image-20230131192300386 image-20230131192401446

當前表信息:

image-20230131210212596image-20230131210234150

可以看到用戶表的餘額減少了,但是商品庫存表的庫存沒有改變。這就產生了數據不一致問題,因此要使用事務。

  1. 改進GoodsService的業務方法,使用聲明式事務:
/**
 * 1.使用註解 @Transactional 可以進行聲明式事務控制
 * 2.該註解會將標識方法中,對資料庫的操作 作為一個事務來管理
 * 3.@Transactional 底層是使用的仍然是AOP機制
 * 4.底層是使用動態代理對象來調用 buyGoodsByTx()方法
 * 5.在執行 buyGoodsByTx()方法前,先調用事務管理器的 doBegin()方法,再調用目標方法
 *   如果執行沒有發生異常,就調用事務管理器 doCommit()方法,否則調用 doRollback()方法
 * @param userId
 * @param goodsId
 * @param amount
 */
@Transactional
public void buyGoodsByTx(int userId, int goodsId, int amount) {
    //輸出購買的相關信息
    System.out.println("用戶購買信息 userId=" + userId
            + " goodsId=" + goodsId + " 購買數量=" + amount);

    //1.得到商品價格
    Float price = goodsDao.queryPriceById(goodsId);
    //2.減少用戶餘額
    goodsDao.updateBalance(userId, price * amount);
    //3.減少商品庫存量
    goodsDao.updateAmount(goodsId, amount);

    System.out.println("用戶購買成功...");
}
  1. 之前的基礎上,在容器文件中配置事務管理器,並啟用基於註解的聲明式事務管理功能
<!--配置事務管理器-對象
   1.DataSourceTransactionManager 這個對象是進行事務管理的
   2.一定要配置數據源屬性,即指定該事務管理器 是對哪個數據源進行事務控制
-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
      id="transactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

<!--配置:啟用基於註解的聲明式事務管理功能-->
<tx:annotation-driven transaction-manager="transactionManager"/>

註意:這裡的 annotation-driven 標簽要選擇以tx結尾的

image-20230131211851101
  1. 再次測試
@Test
public void buyGoodsTestByTx() {
    ApplicationContext ioc =
            new ClassPathXmlApplicationContext("tx.xml");
    GoodsService goodsService = ioc.getBean(GoodsService.class);
    goodsService.buyGoodsByTx(1,1,10);
}

測試結果:可以看到仍然出現異常(因為之前在sql語句中故意添加了錯誤字元)

image-20230131212146965

測試前數據:

image-20230131210602751 image-20230131210619874

測試後數據:

image-20230131212215971 image-20230131212236499

表數據在測試前後數據一致。這說明事務控制起作用了,在出現異常時進行了回滾,因此數據沒有被改變。

2.4聲明式事務機制-Debug

在整個聲明式事務中,DataSourceTransactionManager類尤為重要。

我們可以看到在 DataSourceTransactionManager 的源碼中,有一個 DataSource 屬性,即數據源對象。因為連接是在 DataSource 中獲取,而事務管理器通過連接才能進行事務管理。

image-20230131213707516

此外,DataSourceTransactionManager 還有很多重要的方法:doBegin(),doCommit(),doRollback()等。


debug-1-異常情況

  1. 以 2.3 的代碼為例,在doBegin方法旁打上斷點。

    image-20230131215444956
  2. debug測試方法:buyGoodsTestByTx()

    @Test
    public void buyGoodsTestByTx() {
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("tx.xml");
        GoodsService goodsService = ioc.getBean(GoodsService.class);
        goodsService.buyGoodsByTx(1,1,10);
    }
    
  3. 游標首先跳轉到doBegin方法,點擊Step Over,當運行到下麵的代碼時,可以看到con.getAutoCommit() 的值為true,即此時事務預設自動提交:

    image-20230131220226525
  4. 繼續點擊Step Over,當運行了 con.setAutoCommit(false); 後,可以看到 con.getAutoCommit() 的值變成了false,此時事務不再進行自動提交:

    image-20230131220838858
  5. 在GoodsService的方法旁添加第二個斷點,點擊 Resume Program

    image-20230131221322131 image-20230131221530299

  6. 游標跳轉到第二個斷點處,說明程式是先執行了doBegin()方法,再執行的bugGoodsByTx()方法。

    image-20230131221732867
  7. 在事務管理器的doRollback方法中打上第三個斷點

    image-20230131222007152
  8. 繼續點擊step Over,當bugGoodsByTx()方法執行到 goodsDao.updateAmount(goodsId, amount); 時,游標跳轉到了第三個斷點處!最終在該方法中,執行了 con.rollback(),進行回滾。

    image-20230131222736805

debug-2-正常的流程

修改之前的sql語句,將其變回正確的SQL。在事務管理器的doCommit方法中添加斷點,然後點擊debug。

游標仍然先進入到doBegin方法中,將自動事務提交修改為false後,又調轉到目標方法。這次執行完目標方法後,游標跳轉到了doCommit()方法中。在沒有出現異常的情況下,執行了事務提交。

image-20230131223839605

總結:

在執行目標方法 buyGoodsByTx() 前,先調用事務管理器的 doBegin() 方法,再調用目標方法。如果執行沒有發生異常,就調用事務管理器 doCommit()方法,否則調用 doRollback()方法。

3.事務的傳播機制

事務的傳播機制說明:

  1. 當有多個事務處理並存時,如何控制?

  2. 比如用戶去購買兩次商品(使用不同的方法),每個方法都是一個事務,那麼如何控制呢?

    image-20230131224401793

    也就是說,某個方法本身是一個事務,然後該方法中又調用了其他一些方法,這些方法也是被@Transactional 修飾的,同樣是事務。

  3. 問題在於:裡層方法的事務是被外層方法事務管理?還是它本身作為一個獨立的事務呢?這就涉及到事務的傳播機制問題。

3.1事務傳播機制種類

事務傳播的屬性 / 種類:

傳播屬性 說明
REQUIRED (預設)如果有事務在運行,當前的方法就在這個事務內運行,否則,就啟動一個新的事務,並且在自己的事務內運行
REQUIRES_NEW 當前的方法必須啟動新事務,併在它自己的事務內運行,如果有事務正在運行,應該將它掛起
SUPPORTS 如果有事務在運行,當前的方法就在這個事務內運行,否則它可以不運行在事務中
NOT_SUPPORTED 當前的方法不應該運行在事務中,如果有運行的事務,將它掛起
MANDATORY 當前的方法必須運行在事務內部,如果沒有正在運行的事務,就拋出異常
NEVER 當前的方法不應該運行在事務中,如果有運行的事務,就拋出異常
NESTED 如果有事務在運行,當前的方法就應該在這個事務的嵌套事務內運行,否則,就啟動一個新的事務,併在它自己的事務內運行

常用的就是前面兩種:(1)REQUIRED,(2)REQUIRES_NEWREQUIRES_NEW

其他的不常用


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

-Advertisement-
Play Games
更多相關文章
  • DHTMLX提供有效且專業設計的JavaScript/HTML5工具,使開發人員能夠以更少的時間和精力創建具有豐富界面和快速性能的複雜Web和移動應用程式。 ...
  • 1.CSS 參考手冊 2.元素的分類 首先我們要知道一共有幾種元素 1.行內元素(可以與其他行內元素位於同一行,不會以新行開始高度、寬度不能設置) 2.塊級元素(每個塊級元素都從新的一行開始,其後的元素也另起一行。預設排列方式:從上至下元素的高度、寬度、行高、內外邊距都可設置) 3.行內塊元素(和其 ...
  • refs 字元串形式refs-過時了 // ref key為自命名內容, value為節點 input class Demo extends React.Component { showData = () => { // 拿到的是真實DOM const { input1 } = this.refs; ...
  • 大部分同學都知道,在 CSS 世界中,有 vw、vh、vmax、vmin 這幾個與視口 Viewport 相關的單位。 正常而言: 1vw 等於1/100的視口寬度 (Viewport Width) 1vh 等於1/100的視口高度 (Viewport Height) vmin — vmin 的值是 ...
  • JQuery動態生成的按鈕無法觸發問題與解決方法 起因: 利用JQuery動態添加的按鈕無法通過$(selector).click方法觸發點擊事件 //在網頁載入完成後動態添加表格 $(function () { //通過Ajax向後臺請求程式 $.ajax({ method : "post", u ...
  • 金融社區優惠文章是基於京東商城優惠商品批量化自動生成的,每日通過不同的渠道獲取到待生成的SKU列表,並根據條件生成優惠文章。 但是,生成優惠文章之後續衍生問題:該商品無優惠了,對應文章需要做取消推薦或下架處理,怎樣能更快的知道該商品無優惠了呢? ...
  • 一、Spring簡介 1、Spring介紹 Spring 是一款主流的 Java EE 輕量級開源框架 ,Spring 由“Spring 之父”Rod Johnson 提出並創立,其目的是用於簡化 Java 企業級應用的開發難度和開發周期。Spring的用途不僅限於伺服器端的開發。從簡單性、可測試性 ...
  • 最近做的一個項目是採用前後端分離模式寫前端,後端是fabric區塊鏈,提供介面,需要使用post方法進行訪問。如上一章註冊用戶,就是需要把用戶名、賬戶信息轉換成json形式 使用post方法傳給後端區塊鏈的url.話不多說直接看代碼分析。 /*post1註冊用戶*/ func signup(url ...
一周排行
    -Advertisement-
    Play Games
  • 基於.NET Framework 4.8 開發的深度學習模型部署測試平臺,提供了YOLO框架的主流系列模型,包括YOLOv8~v9,以及其系列下的Det、Seg、Pose、Obb、Cls等應用場景,同時支持圖像與視頻檢測。模型部署引擎使用的是OpenVINO™、TensorRT、ONNX runti... ...
  • 十年沉澱,重啟開發之路 十年前,我沉浸在開發的海洋中,每日與代碼為伍,與演算法共舞。那時的我,滿懷激情,對技術的追求近乎狂熱。然而,隨著歲月的流逝,生活的忙碌逐漸占據了我的大部分時間,讓我無暇顧及技術的沉澱與積累。 十年間,我經歷了職業生涯的起伏和變遷。從初出茅廬的菜鳥到逐漸嶄露頭角的開發者,我見證了 ...
  • C# 是一種簡單、現代、面向對象和類型安全的編程語言。.NET 是由 Microsoft 創建的開發平臺,平臺包含了語言規範、工具、運行,支持開發各種應用,如Web、移動、桌面等。.NET框架有多個實現,如.NET Framework、.NET Core(及後續的.NET 5+版本),以及社區版本M... ...
  • 前言 本文介紹瞭如何使用三菱提供的MX Component插件實現對三菱PLC軟元件數據的讀寫,記錄了使用電腦模擬,模擬PLC,直至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1. PLC開發編程環境GX Works2,GX Works2下載鏈接 https:// ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • 1、jQuery介紹 jQuery是什麼 jQuery是一個快速、簡潔的JavaScript框架,是繼Prototype之後又一個優秀的JavaScript代碼庫(或JavaScript框架)。jQuery設計的宗旨是“write Less,Do More”,即倡導寫更少的代碼,做更多的事情。它封裝 ...
  • 前言 之前的文章把js引擎(aardio封裝庫) 微軟開源的js引擎(ChakraCore))寫好了,這篇文章整點js代碼來測一下bug。測試網站:https://fanyi.youdao.com/index.html#/ 逆向思路 逆向思路可以看有道翻譯js逆向(MD5加密,AES加密)附完整源碼 ...
  • 引言 現代的操作系統(Windows,Linux,Mac OS)等都可以同時打開多個軟體(任務),這些軟體在我們的感知上是同時運行的,例如我們可以一邊瀏覽網頁,一邊聽音樂。而CPU執行代碼同一時間只能執行一條,但即使我們的電腦是單核CPU也可以同時運行多個任務,如下圖所示,這是因為我們的 CPU 的 ...
  • 掌握使用Python進行文本英文統計的基本方法,並瞭解如何進一步優化和擴展這些方法,以應對更複雜的文本分析任務。 ...
  • 背景 Redis多數據源常見的場景: 分區數據處理:當數據量增長時,單個Redis實例可能無法處理所有的數據。通過使用多個Redis數據源,可以將數據分區存儲在不同的實例中,使得數據處理更加高效。 多租戶應用程式:對於多租戶應用程式,每個租戶可以擁有自己的Redis數據源,以確保數據隔離和安全性。 ...