Spring-04 宣告式事務
1、事務的定義
事務就是由一組邏輯上緊密關聯的多個作業單元(資料庫操作)而合并成一個整體,這些操作要么都執行,要么都不執行,
2、事務的特性:ACID
1)原子性A :原子即不可再分,表現:一個事務涉及的多個操作在業務邏輯上缺一不可,保證同一個事務中的操作要不都提交,要不都不提交;
2)一致性C :資料的一致性,一個事務中,不管涉及到多少個操作,都必須保證資料提交的正確性(一致);即:如果在事務資料處理中,有一個或者幾個操作失敗,必須回退所有的資料操作,恢復到事務處理之前的統一狀態;
3)隔離性I :程式運行程序中,事務是并發執行的,要求每個事務之間都是隔離的,互不干擾;
4)持久性D :事務處理結束,要將資料進行持久操作,即永久保存,
3、事務的分類:
1)編程式事務-使用jdbc原生的事務處理,可以將事務處理寫在業務邏輯代碼中,違背aop原則,不推薦;
2)宣告式事務-使用事務注解 @Transactional,可以宣告在方法上,也可以宣告在類上;
-
**優先級**: * <mark>宣告在**類上**,會對**當前類內的所有方式生效**(所有方法都有事務處理);</mark> * <mark>宣告在**方法上**,只會**對當前方法生效**,當類上和方法上同時存在,**方法的優先級高于類**(有些方法,對宣告式事務做特殊屬性配置);</mark>
4、事務的屬性:
4.1 事務的傳播行為:propagation屬性
事務的傳播行為:propagation 屬性指定;
當一個帶事務的方法被另一個帶事務的方法呼叫時(事務嵌套),當前事務如何處理:
-
propagation = Propagation.REQUIRED :
- 默認值,使用呼叫者的事務(全程就一個事務,如果有事務嵌套,以外部事務為主);
-
propagation = Propagation.REQUIRES_NEW :
- 將呼叫者的事務直接掛起,自己重開新的事務處理,結束提交事務,失敗回滾;(當事務嵌套時,內層事務,會重新開啟新事務的處理,不受外部事務的管理);
4.2 事務的隔離級別:isolation屬性
事務的隔離級別:isolation屬性指定隔離級別,只有InnoDB支持事務,所有這里說的事務隔離級別指的是InnoDB下的事務隔離級別,
1、讀未提交 : 讀取其它事務未提交的資料,了解,基本不會使用;
2、讀已提交 : oracle的默認事務隔離級別,同一個事務處理中,只能讀取其它事務提交后的資料(也就是說事務提交之前對其余事務不可見);
3、可重復讀 : mysql默認事務隔離級別,同一個事務處理中,多次讀取同一資料是都是一樣的,不受其它事務影響;
4、串行化 : 可以避免上面所有并發問題,但是執行效率最低,資料一致性最高;
4.3 事務的指定回滾和不會滾
事務的指定回滾和不會滾:Spring在默認的情況下,是對所有的運行時例外會執行事務回滾
1、 rollbackFor : 指定回滾例外,只有產生了指定的例外型別,才會回滾事務;
2、 noRollbackFor : 指定不會滾例外,產生了指定的例外型別,也不會回滾事務;
4.4 事務的超時時長-了解
1、timeout,指定事務出現例外,沒有及時回滾,單位是秒,防止事務超時,占用資源;
4.5 事務的只讀-了解
1、readOnly=false,默認,可讀可寫‘;
2、readOnly=true,代表該事務處理,理論上只允許讀取,不能修改(只是通知spring,并不是一個強制選項)
目的就是:提示資料庫驅動程式和資料庫系統,這個事務并不包含更改資料的操作,那么JDBC驅動程式和資料庫就有可能根據這種情況對該事務進行一些特定的優化,比方說不安排相應的資料庫鎖,以減輕事務對資料庫的壓力,畢竟事務也是要消耗資料庫的資源的,
但是你非要在“只讀事務”里面修改資料,也并非不可以,只不過對于資料一致性的保護不像“讀寫事務”那樣保險而已,
5、 環境搭建
5.1主要 jar包
<spring.version>4.3.18.RELEASE</spring.version>
<mysql.version>5.1.47</mysql.version>
<c3p0.version>0.9.5.2</c3p0.version>
<!-- transaction begin -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring.version}</version>
</dependency>
<!--c3p0資料源 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>${c3p0.version}</version>
</dependency>
<!-- 最主要的是 spring-tx-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- transaction end -->
<!-- mysql begin -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!-- mysql end -->
5.2 組態檔
<?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.kgc.spring"></context:component-scan>
<!-- spring框架讀取外部組態檔-->
<!-- 方式一-->
<bean >
<!-- 指定組態檔的位置,classpath:是類路徑,只有spring可識別 -->
<property name="location" value="https://www.cnblogs.com/xiaoqigui/archive/2022/09/01/classpath:jdbc.properties"></property>
</bean>
<!-- c3p0 資料庫配置,可以管理資料庫連接,還可以自動重連 -->
<bean id="dataSource" >
<property name="driverClass" value="https://www.cnblogs.com/xiaoqigui/archive/2022/09/01/${driver}"></property>
<property name="jdbcUrl" value="https://www.cnblogs.com/xiaoqigui/archive/2022/09/01/${url}"></property>
<property name="user" value="https://www.cnblogs.com/xiaoqigui/archive/2022/09/01/${username}"></property>
<property name="password" value="https://www.cnblogs.com/xiaoqigui/archive/2022/09/01/${password}"></property>
</bean>
<!-- Spring框架對JDBC進行封裝,我們使用JdbcTemplate可以方便實作對資料庫的增刪改查操作, -->
<bean id="jdbcTemplate" >
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 資料源事務管理器 -->
<bean id="dataSourceTransactionManager" >
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--
配置宣告式事務注解掃描,掃描所有添加的宣告式事務注解,交給事務管理器進行統一管理;
名稱空間是tx結尾,才可以生效;
transaction-manager屬性是指定當前自定義的事務管理器;
如果事務管理器的id值是transactionManager,可以省略此屬性的指定
-->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"></tx:annotation-driven>
6、測驗
5.1 購買一輛車(沒有事務嵌套)
TES 購買一輛 AudiQ5;
模擬購買一輛車,主要流程:(1,2,3 整體是一個事務)
1、據買家購買汽車編號,獲取汽車詳情;
2、扣汽車的庫存
3、扣買家的余額
5.1.2 主要業務代碼
5.1.2.1 扣用戶余額業務
BuyerServiceImpl
如果買家余額不足,直接回傳;
@Service
public class BuyerServiceImpl implements BuyerService {
@Autowired
private BuyerDao buyerDao;
@Override
public void subBuyerMoneyByName(String buyerName, Car car) {
// 根據買家姓名,查詢買家詳情
Buyer buyer = buyerDao.selectBuyerByName(buyerName);
// 判斷買家余額是否充足,如果不足,不能繼續扣減金額
if(buyer.getMoney() < car.getPrice()){
System.out.println(String.format("****** 買家:%s,余額不足! ------", buyerName));
return; //直接return
}
// 余額充足,執行扣減余額
int row = buyerDao.updateBuyerMoneyById(buyer.getId(), car.getPrice());
System.out.println(String.format("****** 買家:%s,余額扣減成功,影響行數:%s ------", buyerName, row));
}
}
5.1.2.2 扣庫存業務
CarsStockServiceImpl
如果庫存不足,直接回傳;
@Service
public class CarsStockServiceImpl implements CarsStockService {
@Autowired
private CarsStockDao carsStockDao;
@Override
public void subCarsStockBuyId(Car car) {
//根據汽車編號,插敘汽車詳情
CarsStock carsStock = carsStockDao.selectCarsStockByCid(car.getId());
//判斷庫存是否充足,如果不足,不能購買
if(carsStock.getStock() <= 0){
System.out.println("汽車"+car.getName()+"庫存不足");
return; //直接return
}
//庫存足夠,執行庫存減少
int row = carsStockDao.updateCarStockById(car.getId());
System.out.println("------ 汽車"+car.getName()+"庫存扣減成功" + row +" ------");
}
}
5.1.2.3 用戶買車業務
根據 賣家名字,和汽車編號買車;
BuyCarServiceImpl
@Service("buyCarService") //方便從容器中獲取物件
public class BuyCarServiceImpl implements BuyCarService {
@Autowired
private CarDao carDao;
@Autowired
private CarsStockService carStockService;
@Autowired
private BuyerService buyerService;
//根據 賣家名字,和汽車編號買車
@Override
public void buyCar(String buyerName, Integer carId) {
System.out.println(String.format("------ 買家:%s,購買汽車編號:%s 開始 ------",buyerName,carId));
// 根據買家購買汽車編號,獲取汽車詳情
Car car = carDao.selectCarById(carId);
// 扣買家的余額
buyerService.subBuyerMoneyByName(buyerName, car);
// 扣汽車的庫存
carStockService.subCarsStockBuyId(car);
System.out.println(String.format("------ 買家:%s,購買汽車編號:%s 結束 ------",buyerName,carId));
}
}
5.1.3 測驗(沒有添加事務處理)
5.1.3.1 測驗前的資料
- 汽車價格

- 用戶余額

- 庫存

根據觀察,發現用戶TES的余額不夠買AudiQ5;
5.1.3.2 測驗
//沒有添加事務處理
@Test
public void testSpringUnUsedTx(){
//獲取買車的業務實作物件
BuyCarService buyCarService = context.getBean("buyCarService", BuyCarService.class);
//呼叫買車的業務方法
buyCarService.buyCar("TES",1);
}
運行結果:

5.1.3.3 測驗后的資料
- 用戶余額

- 庫存

5.1.4 測驗 (加上@Transactional 注解添加事務處理)
5.1.4.1 方法上加上@Transactional 注解
@Transactional
public void buyCar(String buyerName, Integer carId) {
...
}
5.1.4.1 測驗
恢復初始資料后測驗;

5.1.5 測驗 (增加例外拋出)
余額不足,沒有例外直接return,不能觸發事務;
需要拋出自定義例外才會觸發事務處理;
5.1.5.1 自定義例外類
BuyCarException
public class BuyCarException extends RuntimeException {
//生成所有的構造方法
public BuyCarException() {
}
public BuyCarException(String message) {
super(message);
}
public BuyCarException(String message, Throwable cause) {
super(message, cause);
}
public BuyCarException(Throwable cause) {
super(cause);
}
public BuyCarException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
5.1.5.2 拋出例外
當余額或庫存不足的時候,拋出自定義例外;
BuyerServiceImpl
@Service
public class BuyerServiceImpl implements BuyerService {
@Autowired
private BuyerDao buyerDao;
@Override
public void subBuyerMoneyByName(String buyerName, Car car) {
Buyer buyer = buyerDao.selectBuyerByName(buyerName);
if(buyer.getMoney() < car.getPrice()){
System.out.println(String.format("****** 買家:%s,余額不足! ------", buyerName));
//return; //沒有例外直接return,不能觸發事務
//余額不足拋出自定義例外
//*****余額充足,執行扣減余額*****
throw new BuyCarException(String.format("****** 買家:%s,余額不足! ------", buyerName));
}
int row = buyerDao.updateBuyerMoneyById(buyer.getId(), car.getPrice());
System.out.println(String.format("****** 買家:%s,余額扣減成功,影響行數:%s ------", buyerName, row));
}
}
CarsStockServiceImpl
@Service
public class CarsStockServiceImpl implements CarsStockService {
@Autowired
private CarsStockDao carsStockDao;
@Override
public void subCarsStockBuyId(Car car) {
CarsStock carsStock = carsStockDao.selectCarsStockByCid(car.getId());
if(carsStock.getStock() <= 0){
System.out.println("汽車"+car.getName()+"庫存不足");
//return; //沒有例外直接return,不能觸發事務
//*****庫存不足,執行扣減余額*****
throw new BuyCarException("汽車"+car.getName()+"庫存不足");
}
int row = carsStockDao.updateCarStockById(car.getId());
System.out.println("------ 汽車"+car.getName()+"庫存扣減成功" + row +" ------");
}
}
5.1.5.3 測驗
恢復初始資料后測驗;

5.1.5.4 測驗 (余額充足)
5.1.5.4.1 測驗前的資料
- 用戶余額

- 庫存

5.1.5.4.2測驗

5.1.5.4.3 測驗后的資料
- 用戶余額

- 庫存

5.2購買兩輛車(有事務嵌套) **對程序理解還有問題
JDG 購買一輛 AudiQ5 和一輛 BmwX3
模擬購物車一次購買兩輛車,主要流程:(1,2 整體式一個事務)
1、買第一輛車(1.1,1.2,1.3 整體是一個事務)
? 1.1 據買家購買汽車編號,獲取汽車詳情;
? 1.2扣汽車的庫存
? 1.3扣買家的余額
2、買第二輛車(1.1,1.2,1.3 整體是一個事務)
? 1.1 據買家購買汽車編號,獲取汽車詳情;
? 1.2扣汽車的庫存
? 1.3扣買家的余額
5.2.1 主要業務代碼
模擬購物車一次購買兩輛車;
多次呼叫購買一輛汽車業務;
BuyCarCartServiceImpl
@Service("BuyCarCartService" )
public class BuyCarCartServiceImpl implements BuyCarCartService {
@Autowired
private BuyCarService buyCarService;
@Override
@Transactional //購物車外層事務注解,buyCarService介面方法中也有事務注解
public void buyCarCart(String buyerName, List<Integer> carIds) {
//模擬購物車垢面多輛車,方便演示事務傳播行為,一輛一輛購買(單獨呼叫買車介面)
carIds.forEach(carId -> buyCarService.buyCar(buyerName,carId));
}
}
5.2.1 propagation = Propagation.REQUIRED
默認傳播特性,以外部事務為主;propagation = Propagation.REQUIRED 可以不寫;
5.2.1.1 測驗前的資料
- 汽車價格

- 用戶余額

- 庫存

5.2.1.2測驗
//測驗事務存在 事務嵌套 的傳播行為
//購物車結算
@Test
public void testSpring(){
BuyCarCartService buyCarCartService = context.getBean("BuyCarCartService", BuyCarCartService.class);
//呼叫購物車買車的業務方法
buyCarCartService.buyCarCart("JDG", Arrays.asList(1,2));
}
測驗結果:

5.2.1.3測驗后的資料
- 用戶余額

- 庫存

5.2.1.4 總結
通過查看資料庫的資料發現,資料沒有改變,說明事務并事務已經回滾,也就是說默認傳播特性,以外部事務為主;
5.2.3 propagation = Propagation.REQUIRES_NEW
propagation = Propagation.REQUIRES_NEW的傳播特性,內部事務會自己重開新的事務處理;
5.2.3.1 內部事務注解添加屬性引數
buyCar方法
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void buyCar(String buyerName, Integer carId) {
...
}
5.2.3.2 測驗
恢復資料再測驗;

5.2.3.3測驗后的資料
- 用戶余額

- 庫存

5.2.3.4 總結
通過查看資料庫的資料發現,資料發生改變,說明內部事務重新開起新的事務處理,不受外部事務的管理;
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/503355.html
標籤:其他
上一篇:Vector底層實作
下一篇:day01-GUI坦克大戰01
