Spring系列七:聲明式事務

🐘聲明式事務

和AOP有密切的聯系, 是AOP的一個實際的應用.

🐲事務分類簡述

●分類
1.編程式事務:
示意代碼, 傳統方式
Connection connection = JdbcUtils.getConnection();
try {
????//1.先設置事務不要自動提交
????connection.setAutoCommit(false);
????//2.進行各種crud
????//多個表的修改, 添加, 刪除
????//3.提交
????connection.commit();
} catch (Exception e) {
????//4.回滾
????connection.rollback();
}

🐲聲明式事務案例


需求分析

我們需要去處理用戶購買商品的業務邏輯. 分析: 當一個用戶要去購買商品應該包含三個步驟

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

其實我們也可以看到, 這時, 需要涉及到三張表: 用戶表, 商品表, 商品存量表. 應該使用事務管理.


解決方案分析

1.使用傳統的編程事務來處理, 將代碼寫到一起 [缺點: 代碼冗余, 效率低, 不利于擴展, 優點是簡單, 好理解]

Connection connection = JdbcUtils.getConnection();
try {
????//1.先設置事務不要自動提交
????connection.setAutoCommit(false);
????//2.進行各種crud
????//多個表的修改, 添加, 刪除
????select from 商品表 => 獲取價格
????//修改用戶余額 update…
????//修改庫存量 update
????//3.提交
????connection.commit();
} catch (Exception e) {
????//4.回滾
????connection.rollback();
}

2.使用Spring的聲明式事務處理, 可以將上面三個子步驟分別寫成一個方法, 然后統一管理. [這是Spring很牛的地方, 在開發使用很多, 優點是無代碼冗余, 效率高, 擴展方便, 缺點是理解較困難 ==> 底層使用AOP(動態代理+動態綁定+反射+注解) => 看Debug源碼]


代碼實現

1.先創建商品系統的數據庫和表

USE spring;-- 用戶表
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(1, '張三', 300);
INSERT INTO user_account VALUES(2, '李四', 400);UPDATE user_account SET money = money - 1 WHERE user_id = '1';
SELECT * FROM user_account;-- 商品表
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(1, '電風扇', '13.5');
INSERT INTO goods VALUES(2, '小臺燈', '15.5');SELECT price FROM goods WHERE goods_id = 1;-- 商品庫存表
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, 100);
INSERT INTO goods_amount VALUES(2, 200);
INSERT INTO goods_amount VALUES(3, 300);UPDATE goods_amount SET goods_num = goods_num - 1 WHERE goods_id = '1';
SELECT * FROM goods_amount;

2.在spring項目com.zzw.spring.tx.dao包下新建GoodsDao

@Repository //將GoodsDao-對象 注入到spring容器
public class GoodsDao {@Resourceprivate JdbcTemplate jdbcTemplate;/*** 根據商品id返回價格* @param id* @return*/public Float queryPriceById(Integer goods_id) {String sql = "SELECT price FROM goods WHERE goods_id = ?";Float price = jdbcTemplate.queryForObject(sql, Float.class, goods_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, Integer amount) {String sql = "UPDATE goods_amount SET goods_num = goods_num - ? WHERE goods_id = ?";jdbcTemplate.update(sql, amount, goods_id);}
}

3.src目錄下, 新建容器配置文件 tx_ioc.xml

<?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"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"><!--配置要掃描的包--><context:component-scan base-package="com.zzw.spring.tx.dao"/>
</beans>

4.在com.zzw.spring.tx包下新建測試類TxTest

public class TxTest {@Testpublic void queryPriceById() {//獲取到容器ApplicationContext ioc =new ClassPathXmlApplicationContext("tx_ioc.xml");GoodsDao goodsDao = ioc.getBean(GoodsDao.class);Float price = goodsDao.queryPriceById(1);System.out.println("id等于1的價格=" + price);}
}

結果報錯 No qualifying bean of type 'org.springframework.jdbc.core.JdbcTemplate' available

因為沒有注入JdbcTemplate對象, 正確配置tx_ioc.xml文件

<?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"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"><!--配置要掃描的包--><context:component-scan base-package="com.zzw.spring.tx.dao"/><!--引入外部的jdbc.properties文件--><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對象--><!--JdbcTemplate會使用到DataSource, 而DataSource可以拿到連接去操作數據庫--><bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate"><!--給JdbcTemplate對象配置dateSource--><property name="dataSource" ref="dataSource"/></bean>
</beans>

再次測試

public class TxTest {@Testpublic void queryPriceById() {//獲取到容器ApplicationContext ioc =new ClassPathXmlApplicationContext("tx_ioc.xml");GoodsDao goodsDao = ioc.getBean(GoodsDao.class);Float price = goodsDao.queryPriceById(1);System.out.println("id等于1的價格=" + price);}@Testpublic void updateBalance() {//獲取到容器ApplicationContext ioc =new ClassPathXmlApplicationContext("tx_ioc.xml");GoodsDao goodsDao = ioc.getBean(GoodsDao.class);goodsDao.updateBalance(1, 1f);System.out.println("用戶余額減少成功");}@Testpublic void updateAmount() {//獲取到容器ApplicationContext ioc =new ClassPathXmlApplicationContext("tx_ioc.xml");GoodsDao goodsDao = ioc.getBean(GoodsDao.class);goodsDao.updateAmount(1, 1);System.out.println("商品減少庫存量成功");}
}

5.在com.zzw.spring.tx.service包下新建GoodsService, 驗證不使用事務就會出現數據不一致現象.

@Service //將GoodsService-對象 注入到spring容器
public class GoodsService {//定義屬性GoodsDao@Resourceprivate GoodsDao goodsDao;/*** 編寫一個方法, 完成用戶購買商品的業務* 這里主要是講解事務管理* @param userId  用戶id* @param goodsId 商品id* @param amount   購買數量*/public void buyGoods(int userId, int goodsId, int amount) {//輸出購買相關信息System.out.println("用戶購買信息 用戶id=" + userId + ", 商品id=" +goodsId + ", 購買數量=" + amount);//1.得到商品的價格Float price = goodsDao.queryPriceById(goodsId);//2.減少用戶的余額goodsDao.updateBalance(userId, price * amount);//3.減少庫存量goodsDao.updateAmount(goodsId, amount);System.out.println("用戶購買成功");}
}

修改xml要掃描的包

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

測試

public class TxTest {//測試用戶購買商品業務@Testpublic void buyGoodsTest() {//獲取到容器ApplicationContext ioc =new ClassPathXmlApplicationContext("tx_ioc.xml");GoodsService goodsService = ioc.getBean(GoodsService.class);goodsService.buyGoods(1,1,1);}
}

驗證不使用事務就會出現數據不一致現象. 故意修改MonsterDaoupdateAmount語句

參考: 家居購, 數據不一致問題

/*** 修改商品的庫存量* @param goods_id* @param amount*/
public void updateAmount(Integer goods_id, Integer amount) {String sql = "UPDATEX goods_amount SET goods_num = goods_num - ? WHERE goods_id = ?";jdbcTemplate.update(sql, amount, goods_id);
}

結論:不能出現部分正確的情況, 要成為一個整體, 要有原子性.


6.加入@Transactional注解

@Service //將GoodsService-對象 注入到spring容器
public class GoodsService {//定義屬性GoodsDao@Resourceprivate GoodsDao goodsDao;/*** @Transactional 注解解讀* 1.使用@Transactional 可以進行聲明式事務控制* 2.即將標識的方法中的對數據庫的操作作為一個事務管理* @param userId* @param goodsId* @param amount*/@Transactionalpublic void buyGoodsByTx(int userId, int goodsId, int amount) {//輸出購買相關信息System.out.println("用戶購買信息 用戶id=" + userId + ", 商品id=" +goodsId + ", 購買數量=" + amount);//1.得到商品的價格Float price = goodsDao.queryPriceById(goodsId);//2.減少用戶的余額goodsDao.updateBalance(userId, price * amount);//3.減少庫存量goodsDao.updateAmount(goodsId, amount);System.out.println("用戶購買成功");}
}

測試

public class TxTest {//測試用戶購買商品業務@Testpublic void buyGoodsTestByTx() {//獲取到容器ApplicationContext ioc =new ClassPathXmlApplicationContext("tx_ioc.xml");GoodsService goodsService = ioc.getBean(GoodsService.class);//這里我們調用的是進行了事務聲明的方法goodsService.buyGoodsByTx(1,1,1);}
}

發現事務沒有發揮作用. 也就是只加一個注解是沒有什么作用的.


解決方案:tx_ioc.xml加入如下配置, 事務得到控制

<?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 https://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.zzw.spring.tx"/><!--引入外部的jdbc.properties文件--><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對象--><!--JdbcTemplate會使用到DataSource, 而DataSource可以拿到連接去操作數據庫--><bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate"><!--給JdbcTemplate對象配置dateSource--><property name="dataSource" ref="dataSource"/></bean><!--配置事務管理器-對象1.DataSourceTransactionManager 這個對象是進行事務管理的2.一定要配置數據源屬性, 這樣指定該事務管理器 是對哪個數據源進行事務控制--><bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager"><property name="dataSource" ref="dataSource"/></bean><!--配置啟用基于注解的聲明式事務管理功能--><tx:annotation-driven transaction-manager="transactionManager"/>
</beans>

在這里插入圖片描述

7.debug事務管理器

/*** @Transactional 注解解讀* 1.使用@Transactional 可以進行聲明式事務控制* 2.即將標識的方法中的對數據庫的操作作為一個事務管理* 3.@Transaction 底層使用的仍然是AOP* 4.底層使用動態代理對象來調用buyGoodsByTx* 5.在執行buyGoodsByTx()方法時, 先調用 事務管理器的 doBegin(), 再調用buyGoodsByTx()*   如果執行沒有發生異常, 則調用 事務管理器的 doCommit(), 如果發生異常, 調用 事務管理器的*   doRollback()* @param userId* @param goodsId* @param amount*/
@Transactional
public void buyGoodsByTx(int userId, int goodsId, int amount) {//輸出購買相關信息System.out.println("用戶購買信息 用戶id=" + userId + ", 商品id=" +goodsId + ", 購買數量=" + amount);//1.得到商品的價格Float price = goodsDao.queryPriceById(goodsId);//2.減少用戶的余額goodsDao.updateBalance(userId, price * amount);//3.減少庫存量goodsDao.updateAmount(goodsId, amount);System.out.println("用戶購買成功");
}

1.在這里打個斷點在這里插入圖片描述

2.開始測試. 運行到doBegin方法
在這里插入圖片描述

在doBegin方法中將connection設置為不自動提交
在這里插入圖片描述

進入buyGoodsByTx方法. 從第一句開始執行
在這里插入圖片描述

🔴
當運行到updateAmount會報錯, 出錯后進入rollback方法
在這里插入圖片描述

進行回滾
在這里插入圖片描述

🔴
如果程序不報錯, 會進入doCommit方法
在這里插入圖片描述

提交
在這里插入圖片描述

doBegin相當于前置通知
doCommit相當于返回通知
doRollback相當于異常通知

🐘事務傳播機制問題

1.當有多個事務處理并存時, 如何控制?
2.比如用戶去購買兩次商品(使用不同的方法), 每個方法都是一個事務, 如何控制呢?

🐲事務傳播機制種類

事務傳播的屬性/種類機制分析, 重點分析 requiredrequires_new 兩種事務傳播屬性, 其它忽略.

傳播屬性描述
REQUIRED如果有事務在運行, 當前的方法就在這個事務內運行. 否則, 就啟動一個新的事務, 并在自己的事務內運行.
REQUIRES_NEW當前的方法必須啟動新事務, 并在它自己的事務內運行, 如果有事務正在運行, 應該將它掛起

🐲事務傳播機制圖解

在這里插入圖片描述
在這里插入圖片描述

在這里插入圖片描述

1.如果設置為REQUIRES_NEW
buyGoods2如果錯誤, 不會影響到 buyGoods(), 反之亦然, 即他們的事務是獨立的
2.如果設置為REQUIRED
buyGoods2buyGoods是一個整體, 只要有方法的事務錯誤, 那么兩個方法都不會執行成功.

🐲事務傳播機制應用實例

需求說明

  • 比如用戶去購買兩次商品(使用不同的方法), 每個方法都是一個事務, 如何控制呢? => 這就是事務的傳播機制

案例

GoodsDao

@Repository //將GoodsDao-對象 注入到spring容器
public class GoodsDao {@Resourceprivate JdbcTemplate jdbcTemplate;/*** 根據商品id返回價格* @param id* @return*/public Float queryPriceById(Integer goods_id) {String sql = "SELECT price FROM goods WHERE goods_id = ?";Float price = jdbcTemplate.queryForObject(sql, Float.class, goods_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, Integer amount) {String sql = "UPDATE goods_amount SET goods_num = goods_num - ? WHERE goods_id = ?";jdbcTemplate.update(sql, amount, goods_id);}public Float queryPriceById2(Integer goods_id) {String sql = "SELECT price FROM goods WHERE goods_id = ?";Float price = jdbcTemplate.queryForObject(sql, Float.class, goods_id);return price;}public void updateBalance2(Integer user_id, Float money) {String sql = "UPDATE user_account SET money = money - ? WHERE user_id = ?";jdbcTemplate.update(sql, money, user_id);}public void updateAmount2(Integer goods_id, Integer amount) {String sql = "UPDATE goods_amount SET goods_num = goods_num - ? WHERE goods_id = ?";jdbcTemplate.update(sql, amount, goods_id);}
}

GoodsService

@Service //將GoodsService-對象 注入到spring容器
public class GoodsService {//定義屬性GoodsDao@Resourceprivate GoodsDao goodsDao;/*** @Transactional 注解解讀* 1.使用@Transactional 可以進行聲明式事務控制* 2.即將標識的方法中的對數據庫的操作作為一個事務管理* 3.@Transaction 底層使用的仍然是AOP* 4.底層使用動態代理對象來調用buyGoodsByTx* 5.在執行buyGoodsByTx()方法時, 先調用 事務管理器的 doBegin(), 再調用buyGoodsByTx()*   如果執行沒有發生異常, 則調用 事務管理器的 doCommit(), 如果發生異常, 調用 事務管理器的*   doRollback()* @param userId* @param goodsId* @param amount*/@Transactionalpublic void buyGoodsByTx(int userId, int goodsId, int amount) {//輸出購買相關信息System.out.println("用戶購買信息 用戶id=" + userId + ", 商品id=" +goodsId + ", 購買數量=" + amount);//1.得到商品的價格Float price = goodsDao.queryPriceById(goodsId);//2.減少用戶的余額goodsDao.updateBalance(userId, price * amount);//3.減少庫存量goodsDao.updateAmount(goodsId, amount);System.out.println("用戶購買成功");}/*** 這個方法是第二套進行商品購買的方法* @param userId* @param goodsId* @param amount*/@Transactionalpublic void buyGoodsByTx2(int userId, int goodsId, int amount) {//輸出購買相關信息System.out.println("用戶購買信息 用戶id=" + userId + ", 商品id=" +goodsId + ", 購買數量=" + amount);//1.得到商品的價格Float price = goodsDao.queryPriceById2(goodsId);//2.減少用戶的余額goodsDao.updateBalance2(userId, price * amount);//3.減少庫存量goodsDao.updateAmount2(goodsId, amount);System.out.println("用戶購買成功");}
}

com.zzw.spring.tx.service包下新建 MultiplyService

@Service
public class MultiplyService {@Resourceprivate GoodsService goodsService;/*** 1.multiBuyGoodsByTx 這個方法 有兩次購買商品的操作* 2.buyGoodsByTx 和 buyGoodsByTx2 都是使用了聲明式事務* 3.當前 buyGoodsByTx 和 buyGoodsByTx2 使用的傳播屬性是默認的, 即REQUIRED*   即會當做一個整體事務管理, 比如buyGoodsByTx方法成功, 但是buyGoodsByTx2失敗,*   會造成整個事務的回滾, 即會回滾buyGoodsByTx.*/public void multiBuyGoodsByTx() {goodsService.buyGoodsByTx(1, 1, 1);goodsService.buyGoodsByTx2(1,1,1);}
}

測試

public class TxTest {//測試事務的傳播機制@Testpublic void multiBuyGoodsByTest() {//獲取到容器ApplicationContext ioc =new ClassPathXmlApplicationContext("tx_ioc.xml");MultiplyService multiplyService = ioc.getBean(MultiplyService.class);multiplyService.multiBuyGoodsByTx();System.out.println("ok");}
}

測試后, 代碼無異常, 此時數據庫表信息如下
在這里插入圖片描述
在這里插入圖片描述


1.將GoodsDao類下的updateAmount2方法 UPDATE改成UPDATEX, 運行后, 代碼回滾, 查看數據庫
在這里插入圖片描述
在這里插入圖片描述

2.在此基礎上, 將GoodsServicebuyGoodsByTxbuyGoodsByTx2 的事務傳播屬性修改成 REQUIRES_NEW. 查看數據庫
在這里插入圖片描述

在這里插入圖片描述

🐘事務隔離級別說明

mysql中的事務隔離級別

1.聲明式事務中, 默認的隔離級別, 就是mysql數據庫默認的隔離級別, 一般為 repeatable read

2.看源碼可知 Isolation.DEFAULT 是: Use the default isolation level of the underlying datastore.
在這里插入圖片描述
在這里插入圖片描述

3.查看數據庫的默認隔離級別 selec t @@global.tx_isolation;

🐲事務隔離級別應用實例

1.修改GoodsService.java, 先測默認隔離級別, 增加方法 buyGoodsByTxISOLATION()

@Service //將GoodsService-對象 注入到spring容器
public class GoodsService {//定義屬性GoodsDao@Resourceprivate GoodsDao goodsDao;/*** 說明* 1.在默認情況下, 聲明式事務的隔離級別是 REPEATABLE READ*/@Transactionalpublic void buyGoodsByTxISOLATION() {//查詢兩次商品的價格Float price = goodsDao.queryPriceById(1);System.out.println("第一次查詢的price=" + price);Float price2 = goodsDao.queryPriceById(1);System.out.println("第二次查詢的price=" + price2);}
}
public class TxTest {//測試聲明式事務的隔離級別@Testpublic void buyGoodsByTxISOLATIONTest() {//獲取到容器ApplicationContext ioc =new ClassPathXmlApplicationContext("tx_ioc.xml");GoodsService goodsService = ioc.getBean(GoodsService.class);goodsService.buyGoodsByTxISOLATION();}
}

1.測試,默認隔離級別: 可重復讀
在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

2.測試,隔離級別: 讀已提交
在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

🐲事務超時回滾

●基本介紹
1.如果一個事務執行的時間超過某個時間限制, 就讓該事務回滾
2.可以通過設置事務超時回滾來實現

案例
1.修改GoodsService.java, 增加buyGoodsByTxTimeout()

@Service //將GoodsService-對象 注入到spring容器
public class GoodsService {//定義屬性GoodsDao@Resourceprivate GoodsDao goodsDao;/*** 解讀* 1.@Transactional(timeout = 2)* 2.timeout = 2 表示 buyGoodsByTxTimeout方法 如果執行時間執行了2秒鐘*   , 該事務就進行回滾.* 3.如果你沒有設置 timeout, 默認是-1, 表示使用事務的默認超時時間*   或者不支持*/@Transactional(timeout = 2)public void buyGoodsByTxTimeout(int userId, int goodsId, int amount) {//輸出購買相關信息System.out.println("用戶購買信息 用戶id=" + userId + ", 商品id=" +goodsId + ", 購買數量=" + amount);//1.得到商品的價格Float price = goodsDao.queryPriceById2(goodsId);//2.減少用戶的余額goodsDao.updateBalance2(userId, price * amount);//3.減少庫存量goodsDao.updateAmount2(goodsId, amount);System.out.println("用戶購買成功");}
}

測試代碼是否正確

public class TxTest {//測試timeout 屬性@Testpublic void buyGoodsByTxTimeoutTest() {//獲取到容器ApplicationContext ioc =new ClassPathXmlApplicationContext("tx_ioc.xml");GoodsService goodsService = ioc.getBean(GoodsService.class);goodsService.buyGoodsByTxTimeout(1,1,1);}
}

模擬超時

/*** 解讀* 1.@Transactional(timeout = 2)* 2.timeout = 2 表示 buyGoodsByTxTimeout方法 如果執行時間執行了2秒鐘*   , 該事務就進行回滾.* 3.如果你沒有設置 timeout, 默認是-1, 表示使用事務的默認超時時間*   或者不支持*/
@Transactional(timeout = 2)
public void buyGoodsByTxTimeout(int userId, int goodsId, int amount) {//輸出購買相關信息System.out.println("用戶購買信息 用戶id=" + userId + ", 商品id=" +goodsId + ", 購買數量=" + amount);//1.得到商品的價格Float price = goodsDao.queryPriceById2(goodsId);//2.減少用戶的余額goodsDao.updateBalance2(userId, price * amount);//模擬超時System.out.println("========超時開始========");try {Thread.sleep(4000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("========超時結束========");//3.減少庫存量goodsDao.updateAmount2(goodsId, amount);System.out.println("用戶購買成功");
}

超時會報異常, 進入doRollback方法, 進行回滾在這里插入圖片描述

🐲課后練習

模擬一個用戶, 進行銀行轉賬購買淘寶商品的業務, 數據表/dao/service自己設計, 保證數據一致性.
1)seller[賣家]
2)buyer[買家]
3)goods[商品表(庫存量)]
4)taoBao[提取入賬成交額的10%]
5)簡單實現, 使用聲明式事務完成
6)要求創建一個新的spring容器配置文件 shopping_ioc.xml, 完成測試


1.sql代碼

-- create database spring_homework;
USE spring_homework;-- 刪除表
DROP TABLE goods;
DROP TABLE seller;
DROP TABLE buyer;
DROP TABLE taoBao;-- 買家表
CREATE TABLE buyer (id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,`name` VARCHAR(32) NOT NULL DEFAULT '',balance DOUBLE NOT NULL DEFAULT 0.0
)CHARSET=utf8;
INSERT INTO buyer VALUES(1, '小明', 500);
-- update buyer set balance = balance + 1 where id = 1;
SELECT * FROM buyer;-- 賣家表
CREATE TABLE seller (id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,`name` VARCHAR(32) NOT NULL DEFAULT '',balance DOUBLE NOT NULL DEFAULT 0.0
)CHARSET=utf8;
INSERT INTO seller VALUES(1, '王守義', 500);
-- update seller set balance = balance + 1 where id = 1;
SELECT * FROM seller;-- 商品表
CREATE TABLE goods (id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,seller_id INT UNSIGNED,`name` VARCHAR(32) NOT NULL DEFAULT '',price DOUBLE NOT NULL DEFAULT 0.0,inventory INT UNSIGNED
)CHARSET=utf8;
INSERT INTO goods VALUES(1, 1, '王守義十三香', 10, 5000);
-- update goods set inventory = inventory - 1 where id = 1;
SELECT * FROM goods WHERE id = 1;-- taoBao表
CREATE TABLE taoBao (id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,scale DOUBLE NOT NULL DEFAULT 0.1,balance DOUBLE NOT NULL DEFAULT 0.0
)CHARSET=utf8;
INSERT INTO taoBao VALUES(1, 0.1, 500);
-- update taoBao set balance = balance + 1 where id = 1;
SELECT * FROM taoBao WHERE id = 1;

2.com.zzw.spring.tx.homework.dao包

@Repository
public class GoodsDao {@Resourceprivate JdbcTemplate jdbcTemplate;/*** 查詢商品價格. 根據商品id, 查詢商品價格* @param id* @return price*/public Double queryPrice(int id) {String sql = "SELECT price FROM goods WHERE id = ?";Double price = jdbcTemplate.queryForObject(sql, Double.class, id);return price;}/*** 更新商品庫存. 根據商品id, 減去庫存* @param id* @param count*/public void updateInventory(int id, int count) {String sql = "update goods set inventory = inventory - ? where id = ?";int affected = jdbcTemplate.update(sql, count, id);System.out.println("affected=" + affected);}
}
@Repository
public class BuyerDao {@Resourceprivate JdbcTemplate jdbcTemplate;/*** 更新買家余額. 從買家表中, 扣除金額* @param id* @param money*/public void updateBalance(int id, double money) {String sql = "UPDATE buyer SET balance = balance - ? WHERE id = ?";int affected = jdbcTemplate.update(sql, money, id);System.out.println("affected=" + affected);}
}
@Repository
public class SellerDao {@Resourceprivate JdbcTemplate jdbcTemplate;/*** 更新賣家余額. 對賣家賬號, 增加金額* @param id* @param money*/public void updateBalance(int id, double money) {String sql = "UPDATE seller SET balance = balance + ? WHERE id = ?";int affected = jdbcTemplate.update(sql, money, id);System.out.println("affected=" + affected);}
}
@Repository
public class TaobaoDao {@Resourceprivate JdbcTemplate jdbcTemplate;/*** 更新淘寶余額. 給id等于1的淘寶賬號, 增加金額* @param id* @param count*/public void updateBalance(int id, double money) {String sql = "UPDATE taoBao SET balance = balance + ? WHERE id = ?";int affected = jdbcTemplate.update(sql, money, id);System.out.println("affected=" + affected);}
}

3.com.zzw.spring.tx.homework.service

@Service
public class GoodsService {@Resourceprivate BuyerDao buyerDao;@Resourceprivate SellerDao sellerDao;@Resourceprivate GoodsDao goodsDao;@Resourceprivate TaobaoDao taobaoDao;//用戶購買商品的行為涉及多張表, 視為一個事務進行管理@Transactionalpublic void buyGoods(int buyerId, int taoBaoId, int sellerId, int goodsId, int count) {//1.查詢商品價格Double price = goodsDao.queryPrice(goodsId);//計算花費多少錢double money = price * count;//2.更新買家余額buyerDao.updateBalance(buyerId, money);3.更新賣家余額. 將成交額的90%轉入賣家余額sellerDao.updateBalance(sellerId, money * 0.9);//4.更新淘寶余額. 將成交額的10%轉入淘寶余額taobaoDao.updateBalance(taoBaoId, money * 0.1);//5.更新商品庫存goodsDao.updateInventory(goodsId, count);System.out.println("用戶 id=" + buyerId + " 在平臺 id=" + taoBaoId + " 商家 id=" + sellerId+ " 購買了商品 id=" + goodsId + " 數量 count=" + count);}
}

4.配置文件
jdbc_homework.properties

jdbc.user=root
jdbc.pwd=zzw
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_homework

shopping_ioc.xml

<?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 https://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.zzw.spring.tx.homework"/><!--引入外部文件 jdbc.properties--><context:property-placeholder location="classpath:jdbc_homework.properties"/><!--配置數據源--><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"><property name="dataSource" ref="dataSource"/></bean><!--配置事務管理器-對象1.DataSourceTransactionManager 這個對象是進行事務管理的2.一定要配置數據源屬性 這樣指定這個事務管理器 是對哪個數據源進行事務控制--><bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager"><property name="dataSource" ref="dataSource"/></bean><!--啟用基于注解的聲明式事務管理功能--><tx:annotation-driven transaction-manager="transactionManager"/>
</beans>

在這里插入圖片描述

5.測試

public class buyGoodsTest {@Testpublic void buyGoods() {//獲取容器ApplicationContext ioc =new ClassPathXmlApplicationContext("shopping_ioc.xml");GoodsService goodsService = ioc.getBean(GoodsService.class);goodsService.buyGoods(1,1,1,1,2);}
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/41643.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/41643.shtml
英文地址,請注明出處:http://en.pswp.cn/news/41643.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

ZooKeeper的應用場景(分布式鎖、分布式隊列)

7 分布式鎖 分布式鎖是控制分布式系統之間同步訪問共享資源的一種方式。如果不同的系統或是同一個系統的不同主機之間共享了一個或一組資源&#xff0c;那么訪問這些資源的時候&#xff0c;往往需要通過一些互斥手段來防止彼此之間的干擾&#xff0c;以保證一致性&#xff0c;…

島嶼的最大面積(力扣)遞歸 JAVA

給你一個大小為 m x n 的二進制矩陣 grid 。 島嶼 是由一些相鄰的 1 (代表土地) 構成的組合&#xff0c;這里的「相鄰」要求兩個 1 必須在 水平或者豎直的四個方向上 相鄰。你可以假設 grid 的四個邊緣都被 0&#xff08;代表水&#xff09;包圍著。 島嶼的面積是島上值為 1 的…

error_Network Error

此頁面為訂單列表&#xff0c;是混合開發(頁面嵌入在客戶端中) 此頁面為訂單列表&#xff0c;此需求在開發時后端先將代碼發布在測試環境&#xff0c;我在本地調試時調用的后端接口進行聯調沒有任何問題。 此后我將代碼發布在測試環境&#xff0c;在app中打開頁面&#xff0c…

vue echarts中按鈕點擊后修改值 watch數據變化后刷新圖表

1 點擊按鈕 {feature: {myBtn1: {show: true,title: 反轉Y軸,showTitle: true,icon: path://M512 0A512 512 0 1 0 512 1024A512 512 0 0 0 512 0M320 320V192h384v128zM128 416V288h256v128zM320 704V576h384v128zM128 800V672h256v128z,onclick: () > {dataSetting.rever…

nginx服務器報錯502 Bad Gateway的原因以及解決辦法

服務器報錯nginx 502 Bad Gateway的原因以及解決辦法_502 bad gateway nginx_主題模板站的博客-CSDN博客

C++學習筆記總結練習:effective 學習日志

準則 1.少使用define define所定義的常量會在預處理的時候被替代&#xff0c;出錯編譯器不容易找到錯誤。而且還沒有作用范圍限制&#xff0c;推薦使用constdefine宏定義的函數&#xff0c;容易出錯&#xff0c;而且參數需要加上小括號&#xff0c;推薦使用inline有的類中例如…

已經開源的中文大模型對比,支持更新

大模型下載&#xff1a;互鏈高科 ClueAI/PromptCLUE-base-v1-5 at main (huggingface.co) 支持多任務生成&#xff0c;支持中文&#xff0c;不支持多輪對話&#xff0c;體驗&#xff1a;ClueAI (cluebenchmarks.com) 基于promptclue-base進一步訓練的模型&#xff1a;ClueAI/Ch…

【C與C++的相互調用方法】

C與C的相互調用方法 C與C為什么相互調用的方式不同C中調用CC中調用C致謝 C與C為什么相互調用的方式不同 C 和 C 之間的相互調用方式存在區別&#xff0c;主要是由于 C 和 C 語言本身的設計和特性不同。 函數調用和參數傳遞方式不同&#xff1a;C 和 C 在函數調用和參數傳遞方面…

docker oracle linux命令執行sql

docker 安裝參照 https://blog.csdn.net/arcsin_/article/details/123707618 docker container ls -a命令查看容器名 打開容器 docker exec -it orcl19c_03 /bin/bashsys 用戶登錄容器 sqlplus / as sysdbashow pdbs;什么是pdb數據庫&#xff1f;什么是CDB&#xff1f; 參…

游戲如何防御DDOS流量攻擊呢,用游戲盾真的有用么?

針對在線游戲行業來說&#xff0c;DDoS&#xff08;分布式拒絕服務&#xff09;攻擊是一種極具破壞性的威脅。DDoS攻擊可能導致游戲服務器不可用&#xff0c;嚴重影響游戲體驗和運營。為了解決這一問題&#xff0c;游戲盾作為一種專門為游戲行業設計的安全解決方案&#xff0c;…

微信小程序 藍牙設備連接,控制開關燈

1.前言 微信小程序中連接藍牙設備&#xff0c;信息寫入流程 1、檢測當前使用設備&#xff08;如自己的手機&#xff09;是否支持藍牙/藍牙開啟狀態 wx:openBluetoothAdapter({}) 2、如藍牙已開啟狀態&#xff0c;檢查藍牙適配器的狀態 wx.getBluetoothAdapterState({}) 3、添加…

第十三章 SpringBoot項目(總)

1.創建SpringBoot項目 1.1.設置編碼 1.4.導入已有的spring boot項目 2.快速搭建Restfull風格的項目 2.1.返回字符串 RestController public class IndexController {RequestMapping("/demo1")public Object demo1() {System.out.println("demo1 ran...."…

kafka的位移

文章目錄 概要消費位移__consumer_offsets主題位移提交 概要 本文主要總結kafka的位移是如何管理的&#xff0c;在broker端如何通過命令行查看到位移信息&#xff0c;并從代碼層面總結了位移的提交方式。 消費位移 對于 Kafka 中的分區而言&#xff0c;它的每條消息都有唯一…

0基礎學習VR全景平臺篇 第86篇:智慧眼-為什么要設置分組選擇?

一、功能說明 分組選擇&#xff0c;也就是給全景的每個分組去設置其所屬的行政區劃&#xff0c;設置后只有屬于同行政區劃的成員才可進入其場景進行相關操作&#xff0c;更便于實現城市的精細化管理。 二、后臺編輯界面 分組名稱&#xff1a;場景的分組名稱。 對應分類&…

網絡安全--linux下Nginx安裝以及docker驗證標簽漏洞

目錄 一、Nginx安裝 二、docker驗證標簽漏洞 一、Nginx安裝 1.首先創建Nginx的目錄并進入&#xff1a; mkdir /soft && mkdir /soft/nginx/cd /soft/nginx/ 2.下載Nginx的安裝包&#xff0c;可以通過FTP工具上傳離線環境包&#xff0c;也可通過wget命令在線獲取安裝包…

【數據結構與算法】隊列

文章目錄 一&#xff1a;隊列1.1 隊列的概念1.2 隊列的介紹1.3 隊列示意圖 二&#xff1a;數組模擬隊列2.1 介紹2.2 思路2.3 代碼實現2.3.1 定義隊列基本信息2.3.2 初始化隊列2.3.3 判斷隊列是否滿&#xff0c;是否為空2.3.4 添加數據到隊列2.3.5 獲取隊列數據&#xff0c;出隊…

垃圾回收機制

什么是內存泄漏&#xff1f; 內存泄漏是指程序中已經不再使用的內存卻沒有被正確釋放或回收的情況。在編程中&#xff0c;當對象或數據不再被程序使用&#xff0c;但其所占用的內存空間沒有被垃圾回收機制回收&#xff0c;就會導致內存泄漏。 內存泄漏可能會導致程序的內存消…

圖數據庫_Neo4j和SpringBoot整合使用_創建節點_刪除節點_創建關系_使用CQL操作圖譜---Neo4j圖數據庫工作筆記0009

首先需要引入依賴 springboot提供了一個spring data neo4j來操作 neo4j 可以看到它的架構 這個是下載下來的jar包來看看 有很多cypher對吧 可以看到就是通過封裝的驅動來操作graph database 然后開始弄一下 首先添加依賴

【實用黑科技】如何 把b站的緩存視頻弄到本地——數據恢復軟件WinHex 和 音視頻轉碼程序FFmpeg

&#x1f468;?&#x1f4bb;個人主頁&#xff1a;元宇宙-秩沅 &#x1f468;?&#x1f4bb; hallo 歡迎 點贊&#x1f44d; 收藏? 留言&#x1f4dd; 加關注?! &#x1f468;?&#x1f4bb; 本文由 秩沅 原創 &#x1f468;?&#x1f4bb; 收錄于專欄&#xff1a;效率…

onnxruntime 支持的所有后端

1 代碼導出 import onnxruntime as ort aaa ort.get_all_providers() print(aaa)1. 1 下面是ort支持的所有后端 TensorrtExecutionProvider, CUDAExecutionProvider, MIGraphXExecutionProvider, ROCMExecutionProvider, OpenVINOExecutionProvider, DnnlExecutionProvider…