🐘聲明式事務
和AOP有密切的聯系, 是AOP的一個實際的應用.
🐲事務分類簡述
●分類
1.編程式事務:
示意代碼, 傳統方式
Connection connection = JdbcUtils.getConnection();
try {
????//1.先設置事務不要自動提交
????connection.setAutoCommit(false);
????//2.進行各種crud
????//多個表的修改, 添加, 刪除
????//3.提交
????connection.commit();
} catch (Exception e) {
????//4.回滾
????connection.rollback();
}
🐲聲明式事務案例
需求分析
我們需要去處理用戶購買商品的業務邏輯. 分析: 當一個用戶要去購買商品應該包含三個步驟
- 通過商品獲取價格
- 購買商品(某人購買商品, 修改用戶的余額)
- 修改庫存量
其實我們也可以看到, 這時, 需要涉及到三張表: 用戶表, 商品表, 商品存量表. 應該使用事務管理.
解決方案分析
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);}
}
驗證不使用事務就會出現數據不一致現象. 故意修改MonsterDao
的updateAmount
語句
參考: 家居購, 數據不一致問題
/*** 修改商品的庫存量* @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.比如用戶去購買兩次商品(使用不同的方法), 每個方法都是一個事務, 如何控制呢?
🐲事務傳播機制種類
事務傳播的屬性/種類機制分析, 重點分析 required
和 requires_new
兩種事務傳播屬性, 其它忽略.
傳播屬性 | 描述 |
---|---|
REQUIRED | 如果有事務在運行, 當前的方法就在這個事務內運行. 否則, 就啟動一個新的事務, 并在自己的事務內運行. |
REQUIRES_NEW | 當前的方法必須啟動新事務, 并在它自己的事務內運行, 如果有事務正在運行, 應該將它掛起 |
🐲事務傳播機制圖解
1.如果設置為REQUIRES_NEW
buyGoods2如果錯誤, 不會影響到 buyGoods(), 反之亦然, 即他們的事務是獨立的
2.如果設置為REQUIRED
buyGoods2
和buyGoods
是一個整體, 只要有方法的事務錯誤, 那么兩個方法都不會執行成功.
🐲事務傳播機制應用實例
需求說明
- 比如用戶去購買兩次商品(使用不同的方法), 每個方法都是一個事務, 如何控制呢? => 這就是事務的傳播機制
案例
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.在此基礎上, 將GoodsService
的buyGoodsByTx
和 buyGoodsByTx2
的事務傳播屬性修改成 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);}
}