1. 程式人生 > 程式設計 >Spring事務管理原理及方法詳解

Spring事務管理原理及方法詳解

這篇文章主要介紹了Spring事務管理原理及方法詳解,文中通過示例程式碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下

事務,在日常開發或者面試中都必定會涉及到。開發工作中,結合資料庫開發理解就是:一組dml要麼全部成功執行提交,要麼因為某一個操作異常,撤銷之前所做的成功的操作,整體執行失敗。再簡單點的一句話:生死與共。

由此,可以看出,事務的必要性:在開發工作中,保證操作資料的安全性。事務的控制也就是保證資料的訪問安全性。

一、事務的四大特性

A:原子性(atomicity),對資料的修改,要麼全部成功執行,要麼全部不執行。

C:一致性(consistency),一旦事務完成,系統必須保證資料職員是滿足業務狀態的一種一致狀態中。很難懂的解釋,跟原子性很像。一個事務在操作過程中,資料可能會產生很多中間態,一致性保證中間態對其他事務不可見,因為這些中間態,與事務的開始和結束的狀態是不一致的。也就是從一種正確的狀態到另一種正確的狀態。

I:隔離性(isolation),事務之間的執行應不相互影響,也即事務執行的獨立。

D:永續性(durability),事務一旦提交,則對資料庫的修改是永久性的。

二、事務的隔離級別

併發環境下,事務可能會存在若干問題:髒讀、幻讀、不可重複讀、第一類更新丟失、第二類更新丟失。

型別   說明 舉例
髒讀 A事務讀取到了B事務未提交的資料 A開啟事務=>B開啟事務,讀取賬戶1000塊,取走100塊=>A讀取賬戶金額,讀取到900=>B回滾事務。此時A讀取的餘額資料是無效的
幻讀 一個事務裡面的操作發現了未被操作的資料

A開啟事務,修改某些資料狀態=>B開啟事務,執行新增資料並提交=>A事務提交,會出現一條未被修改的資料。

幻讀發生的前提是併發事務中發生了新增或者刪除動作。

不可重複讀 一個事務中,先後兩次讀取資料,讀到的結果不一致

A開啟事務,讀取賬戶1000塊=>B開啟事務,讀取賬戶1000塊,取出100塊並提交事務=>A再讀取賬戶餘額,餘額900塊。

一個事務範圍內的兩次同樣的查詢,卻返回了兩次不同的資料,這就是不可重複讀

第一類更新丟失 A事務撤銷,把已經提交的B事務的更新的資料覆蓋 A開啟事務,讀取賬戶1000塊=>B開啟事務,讀取賬戶1000塊,然後增加100塊,提交事務,賬戶變為1100=>A撤銷回滾事務,賬戶成1000塊
第二類更新丟失 A事務提交,把已經提交的B事務的更新的資料覆蓋 A開啟事務,讀取賬戶1000塊=>B開啟事務,讀取賬戶1000塊,然後增加100塊,提交事務,賬戶變為1100=>A增加100塊,提交事務,賬戶變為1100。

針對併發環境下可能出現的事務問題,於是就出現了隔離級別的解決方案,由低到高依次是:讀未提交(Read uncommitted)、讀已提交(Read committed)、可重複讀(Repeatable read)、序列序列化(serializable)。下表展示出不同的隔離級別,對於髒讀、幻讀、不可重複讀是否會出現。

型別 髒讀 不可重複讀 幻讀 說明
Read uncommitted 會 
Read committed 不會
Repeatable read 不會 不會 mysql的預設隔離級別
serializable 不會 不會 不會 最嚴格的隔離級別,將事務序列化執行,效能低。

mysql中查詢當前隔離級別:select @@tx_isolation;

三、spring事務支援的隔離級別和傳播特性

spring中定義了五種隔離界別和七種傳播行為(可以在org.springframework.transaction.TransactionDefinition類中看到詳細的解釋)

1、spring支援的隔離級別

ISOLATION_DEFAULT:預設級別。一般是使用的是資料庫本身的隔離級別(mysql - Repeatable read 、oracle - Read committed)

餘下ISOLATION_READ_UNCOMMITTED、ISOLATION_READ_COMMITTED、ISOLATION_REPEATABLE_READ、ISOLATION_SERIALIZABLE分別對應上述資料庫隔離級別配置。

2、傳播特性

事務的傳播特性就是在多個事務方法互相呼叫的時候,事務該如何在方法之間使用傳播:

  •     PROPAGATION_REQUIRED:如果當前有事務,則加入該事務中。如果沒有,則新建事務。
  •     PROPAGATION_SUPPORTS:如果當前有事務,則加入該事務中。如果沒有,則以非事務狀態執行。
  •     PROPAGATION_MANDATORY:如果當前有事務,則加入該事務中。如果沒有,則丟擲無事務異常
  •     PROPAGATION_REQUIRES_NEW:新建事務。如果當前存在事務,則掛起該事務。
  •     PROPAGATION_NOT_SUPPORTED:非事務狀態執行。如果當前存在事務,則掛起該事務。
  •     PROPAGATION_NEVER:非事務狀態執行。如果當前存在事務,則丟擲異常。
  •     PROPAGATION_NESTED:如果當前有事務,則巢狀事務(父子事務)執行。如果沒有,類似PROPAGATION_REQUIRED處理。

  spring預設的傳播行為是PROPAGATION_REQUIRED,一般適用於絕大多數的開發工作。

3、事務的超時屬性

事務在超過預定時間內還未完成操作,則自動回滾事務。TransactionDefinition 中以int值來表示超時時間,單位是秒,提供的預設值是TIMEOUT_DEFAULT = -1,即永不超時,一直等待操作完成

四、spring事務配置方法

spring並不具體直接的管理事務,而是提供了一個介面org.springframework.transaction.PlatformTransactionManager,該介面中主要定義了三個方法:getTransaction(獲取事務)、commit(提交)和rollback(回滾)。

根據不同的持久化策略,spring提供了不同的實現,比如jdbc -

org.springframework.jdbc.datasource.DataSourceTransactionManager、hibernate - org.springframework.orm.hibernate5.HibernateTransactionManager等,在其他的實現可以通過原始碼去查詢。

spring事務使用,可以分為程式設計式事務和宣告式事務。程式設計式事務每次業務使用都得書寫獲取事務、設定事務隔離級別和傳播特性、提交或回滾事務,程式碼的重複太高,費時費力,且如果程式碼的功能性複雜時候,使用程式設計式變得更加痛苦。而宣告式的事務,屬於無侵入式的,不會影響主業務流程,且編寫上非常簡單。所以目前開發工作中,更多的是使用宣告式事務。

1、程式設計式事務

//to do。後續補充

2、宣告式事務

宣告式事務分為兩種:基於aop的織入和@Transactional的註解。

2.1、基於aop的事務織入

之後在servcie中定義方法,最好的就是tx:method中定義的格式開頭,就會執行特定的事務。

2.2、註解事務

註解事務第一部分的資料來源和事務管理器配置同上,配置檔案中需要修改的是開啟註解配置:

然後在service程式設計中,加註解@Transactional即可(建議只在service實現類中加),如下是配置樣例,其中的屬性可以按需設定:

@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ,timeout = 100,readOnly = false,rollbackFor = {},noRollbackFor = {})

注意點:

事務的異常回滾只檢查RuntimeException的異常,checked exception(如ClassNotFoundException、FileNotFoundException等)不會滾,捕獲異常不丟擲也不會回滾。

五、spring和springmvc父子容器

現在開發工作中,一般大多數都使用的spring和springmvc構建,這裡,spring容器和springmvc容器就構成了父子容器的關係。父容器spring是發現不了子容器springmvc中的bean的,而子容器可以發現父容器中註冊的bean。由此,實際開發工作中,不注意的話,往往會產生一些意想不到的問題。

首先,通常我們配置spring配置檔案applicationContext.xml的時候,會配置如下的掃描:

<context:component-scan base-package="com.cfang" />

這個配置會掃描指定包下面的所有@Component型別註解,包括@Controller,@Service,@Respository,並將掃描到的bean註冊到spring容器中。

一般,spring配置檔案中,還會出現下面的配置,作用是掃描@Required、@Autowired、 @PostConstruct、@PersistenceContext、@Resource、@PreDestroy等註解。理論上,此配置為可選配置,因為上面的掃描配置會預設開啟。

<context:annotation-config/>

接下來配置springmvc的配置檔案spring-mvc.xml,配置掃描註解@RequestMapping、@RequestBody、@ResponseBody等,同時,該配置預設載入很多引數繫結方法 。

<mvc:annotation-driven />

上面一句話,相當於:

<!--配置註解控制器對映器,它是SpringMVC中用來將Request請求URL到對映到具體Controller-->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>
<!--配置註解控制器對映器,它是SpringMVC中用來將具體請求對映到具體方法-->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>

上面聊完基本配置,以下梳理下,來了解可能產生的容器衝突,對於事務管理的影響。

首先,有兩個基本的容器:spring和springmvc,配置檔案分別為applicationContext.xml和spring-mvc.xml

1、applicationContext.xml中配置<context:component-scan base-package="com.cfang" />,掃描指定包下的所有bean,並自動註冊到spring容器中

2、spring-mvc.xml配置<mvc:annotation-driven />,掃描相關的springmvc的註解

3、為了保證springmvc的正常跳轉,通常我們還得在spring-mvc.xml檔案中配置包掃描<context:component-scan base-package="com.cfang" />。

按照以上配置資訊,就會產生事務失效。原因就在於:

Spring容器優先載入由ServletContextListener(對應applicationContext.xml)產生的父容器,

而SpringMVC(對應spring-mvc.xml)產生的是子容器。子容器Controller進行掃描裝配時裝配的@Service註解的例項是沒有經過事務加強處理,即沒有事務處理能力的Service,

而父容器進行初始化的Service是保證事務的增強處理能力的。如果不在子容器中將Service排除(exclude)掉,此時得到的將是無事務處理能力的Service。

解決辦法按照官方建議的來配置,各自負責一部分載入:

spring掃描:

<context:component-scan base-package="com.cfang.WeChat" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
  </context:component-scan>

springmvc掃描:

<context:component-scan base-package="com.cfang.WeChat" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
  </context:component-scan>

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。