Spring聲明式事務配置使用詳解

序章

Spring 框架對 JDBC 進行封裝,使用 JdbcTemplate 方便實現對數據庫操作

準備工作

<dependencies>
<!-- 基於Maven依賴傳遞性,導入spring-context依賴即可導入當前所需所有jar包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.1</version>
</dependency>
<!-- Spring 持久化層支持jar包 -->
<!-- Spring 在執行持久化層操作、與持久化層技術進行整合過程中,需要使用orm、jdbc、tx三個
jar包 -->
<!-- 導入 orm 包就可以通過 Maven 的依賴傳遞性把其他兩個也導入 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.3.1</version>
</dependency>
<!-- Spring 測試相關 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.1</version>
</dependency>
<!-- junit測試 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- MySQL驅動 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
<!-- 數據源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.31</version>
</dependency>
</dependencies>

創建jdbc.properties

jdbc.user=root
jdbc.password=atguigu
jdbc.url=jdbc:mysql://localhost:3306/ssm
jdbc.driver=com.mysql.cj.jdbc.Driver

配置Spring的配置文件

<!--導入外部屬性文件-->
<context:property-placeholderlocation="classpath:jdbc.properties"/>
<!--配置數據源-->
<beanid="druidDataSource"class="com.alibaba.druid.pool.DruidDataSource"> 
 <propertyname="url"value="${atguigu.url}"/>
  <propertyname="driverClassName"value="${atguigu.driver}"/>
  <propertyname="username"value="${atguigu.username}"/>
  <propertyname="password"value="${atguigu.password}"/>
</bean>
<!--配置JdbcTemplate-->
<beanid="jdbcTemplate"class="org.springframework.jdbc.core.JdbcTemplate">  <!--裝配數據源-->
  <propertyname="dataSource"ref="druidDataSource"/>
</bean>

聲明式事務概念

編程式的實現方式存在缺陷:

細節沒有被屏蔽:具體操作過程中,所有細節都需要程序員自己來完成,比較繁瑣。

代碼復用性不高:如果沒有有效抽取出來,每次實現功能都需要自己編寫代碼,代碼就沒有得到復 用。

聲明式事務

既然事務控制的代碼有規律可循,代碼的結構基本是確定的,所以框架就可以將固定模式的代碼抽取出來,進行相關的封裝。

封裝起來後,我們隻需要在配置文件中進行簡單的配置即可完成操作。

好處1:提高開發效率

好處2:消除瞭冗餘的代碼

好處3:框架會綜合考慮相關領域中在實際開發環境下有可能遇到的各種問題,進行瞭健壯性、性 能等各個方面的優化

代碼講解

配置 Spring 的配置文件

<!--掃描組件-->
<context:component-scanbase-package="com.atguigu.spring.tx.annotation">
</context:component-scan>
<!--導入外部屬性文件-->
<context:property-placeholderlocation="classpath:jdbc.properties"/>
<!--配置數據源-->
<beanid="druidDataSource"class="com.alibaba.druid.pool.DruidDataSource">  
<propertyname="url"value="${jdbc.url}"/>
  <propertyname="driverClassName"value="${jdbc.driver}"/>
  <propertyname="username"value="${jdbc.username}"/>
  <propertyname="password"value="${jdbc.password}"/>
</bean>
<!--配置JdbcTemplate-->
<beanid="jdbcTemplate"class="org.springframework.jdbc.core.JdbcTemplate">
  <!--裝配數據源-->
  <propertyname="dataSource"ref="druidDataSource"/>
</bean>

創建表

REATETABLE`t_book`(
book_id`int(11)NOTNULLAUTO_INCREMENTCOMMENT'主鍵',
`book_name`varchar(20)DEFAULTNULLCOMMENT'圖書名稱',
`price`int(11)DEFAULTNULLCOMMENT'價格',
`stock`int(10)unsignedDEFAULTNULLCOMMENT'庫存(無符號)',
PRIMARYKEY(`book_id`)
)ENGINE=InnoDBAUTO_INCREMENT=3DEFAULTCHARSET=utf8;
insert into`t_book`(`book_id`,`book_name`,`price`,`stock`)values(1,'鬥破蒼穹',80,100),(2,'鬥羅大陸',50,100);
CREATETABLE`t_user`(
`user_id`int(11)NOTNULLAUTO_INCREMENTCOMMENT'主鍵',
`username`varchar(20)DEFAULTNULLCOMMENT'用戶名',
`balance`int(10)unsignedDEFAULTNULLCOMMENT'餘額(無符號)',
PRIMARYKEY(`user_id`)
)ENGINE=InnoDBAUTO_INCREMENT=2DEFAULTCHARSET=utf8;
insert into`t_user`(`user_id`,`username`,`balance`)values(1,'admin',50);

創建組件

創建BookController:

@Controller
public class BookController {
    @Autowired
    private BookService bookService;
    public void buyBook(Integer bookId, Integer userId){
    bookService.buyBook(bookId, userId);
    }
}

創建接口BookService:

public interface BookService {    
void buyBook(Integer bookId, Integer userId);
}

創建實現類BookServiceImpl:

測試無事務情況

②模擬場景

用戶購買圖書,先查詢圖書的價格,再更新圖書的庫存和用戶的餘額

假設用戶id為1的用戶,購買id為1的圖書

用戶餘額為50,而圖書價格為80

購買圖書之後,用戶的餘額為-30,數據庫中餘額字段設置瞭無符號,因此無法將-30插入到餘額字段

此時執行sql語句會拋出SQLException

③觀察結果

因為沒有添加事務,圖書的庫存更新瞭,但是用戶的餘額沒有更新

顯然這樣的結果是錯誤的,購買圖書是一個完整的功能,更新庫存和更新餘額要麼都成功要麼都失敗

加入事務

<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--
開啟事務的註解驅動
通過註解@Transactional所標識的方法或標識的類中所有的方法,都會被事務管理器管理事務
-->
<!-- transaction-manager屬性的默認值是transactionManager,如果事務管理器bean的id正好就
是這個默認值,則可以省略這個屬性 -->
<tx:annotation-driven transaction-manager="transactionManager" />

註意:導入的名稱空間需要 tx 結尾的那個。

添加事務註解

因為service層表示業務邏輯層,一個方法表示一個完成的功能,因此處理事務一般在service層處理在BookServiceImpl的buybook()添加註解@Transactiona

觀察結果

由於使用瞭Spring的聲明式事務,更新庫存和更新餘額都沒有執行

@Transactional註解標識的位置

@Transactional標識在方法上,咋隻會影響該方法

@Transactional標識的類上,咋會影響類中所有的方法

事務屬性:隻讀

對一個查詢操作來說,如果我們把它設置成隻讀,就能夠明確告訴數據庫,這個操作不涉及寫操作。這樣數據庫就能夠針對查詢操作來進行優化。

事務屬性:超時

事務在執行過程中,有可能因為遇到某些問題,導致程序卡住,從而長時間占用數據庫資源。而長時間占用資源,大概率是因為程序運行出現瞭問題(可能是Java程序或MySQL數據庫或網絡連接等等)。

此時這個很可能出問題的程序應該被回滾,撤銷它已做的操作,事務結束,把資源讓出來,讓其他正常程序可以執行

概括來說就是一句話:超時回滾,釋放資源。

事務屬性:回滾策略

聲明式事務默認隻針對運行時異常回滾,編譯時異常不回滾。

可以通過@Transactional中相關屬性設置回滾策略

rollbackFor屬性:需要設置一個Class類型的對象

rollbackForClassName屬性:需要設置一個字符串類型的全類名

noRollbackFor屬性:需要設置一個Class類型的對象

rollbackFor屬性:需要設置一個字符串類型的全類名

事務屬性:事務隔離級別

數據庫系統必須具有隔離並發運行各個事務的能力,使它們不會相互影響,避免各種並發問題。一個事務與其他事務隔離的程度稱為隔離級別。SQL標準中規定瞭多種事務隔離級別,不同隔離級別對應不同的幹擾程度,隔離級別越高,數據一致性就越好,但並發性越弱。

隔離級別一共有四種:

讀未提交:READ UNCOMMITTED

允許Transaction01讀取Transaction02未提交的修改。

讀已提交:READ COMMITTED、

要求Transaction01隻能讀取Transaction02已提交的修改。

可重復讀:REPEATABLE READ

確保Transaction01可以多次從一個字段中讀取到相同的值,即Transaction01執行期間禁止其它

事務對這個字段進行更新。

串行化:SERIALIZABLE

確保Transaction01可以多次從一個表中讀取到相同的行,在Transaction01執行期間,禁止其它

事務對這個表進行添加、更新、刪除操作。可以避免任何並發問題,但性能十分低下。

@Transactional(isolation = Isolation.DEFAULT)//使用數據庫默認的隔離級別
@Transactional(isolation = Isolation.READ_UNCOMMITTED)//讀未提交
@Transactional(isolation = Isolation.READ_COMMITTED)//讀已提交
@Transactional(isolation = Isolation.REPEATABLE_READ)//可重復讀
@Transactional(isolation = Isolation.SERIALIZABLE)//串行化

事務屬性:事務傳播行為

當事務方法被另一個事務方法調用時,必須指定事務應該如何傳播。例如:方法可能繼續在現有事務中運行,也可能開啟一個新事務,並在自己的事務中運行。

可以通過@Transactional中的propagation屬性設置事務傳播行為

修改BookServiceImpl中buyBook()上,註解@Transactional的propagation屬性

@Transactional(propagation = Propagation.REQUIRED),默認情況,表示如果當前線程上有已經開啟的事務可用,那麼就在這個事務中運行。經過觀察,購買圖書的方法buyBook()在checkout()中被調用,checkout()上有事務註解,因此在此事務中執行。所購買的兩本圖書的價格為80和50,而用戶的餘額為100,因此在購買第二本圖書時餘額不足失敗,導致整個checkout()回滾,即隻要有一本書買不瞭,就都買不瞭

@Transactional(propagation = Propagation.REQUIRES_NEW),表示不管當前線程上是否有已經開啟的事務,都要開啟新事務。同樣的場景,每次購買圖書都是在buyBook()的事務中執行,因此第一本圖書購買成功,事務結束,第二本圖書購買失敗,隻在第二次的buyBook()中回滾,購買第一本圖書不受影響,即能買幾本就買幾本

到此這篇關於Spring聲明式事務配置使用詳解的文章就介紹到這瞭,更多相關Spring聲明式事務內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: