13. Spring事務管理簡介


Spring事務管理

事務用於數據庫的訪問,但是一般情況下,需要將事務提升到業務層,即Service層。這樣做是爲了能夠使用事務的特性來管理具體的業務。
在Spring中通常可以通過以下三種方式來實現對事務的管理:

  1. 使用Spring的事務代理工廠管理事務(已廢棄)
  2. 使用Spring的事務註解管理事務(目前最常用)
  3. 使用AspectJ的AOP配置管理事務

Spring事務API

Spring的事務管理,主要用到兩個事務相關的接口。

事務管理器接口

返回目錄
事務管理器是PlatformTransactionManager接口對象。其主要用於完成事務的提交、回滾,以及獲取事務的狀態信息。
該接口定義了3個事務方法:

  • void commit(TransactionStatus status) : 事務的提交
  • TransactionStatus getTransaction(TransactionDefinition definition) : 獲取事務的狀態
  • void roolback(TransactionStatus status) : 事務的回滾

常用的兩個實現類

PlatformTransactionManager接口有兩個常用的事務實現類:

  • DataSourceTransactionManager : 使用JDBC或Mybatis進行持久化數據時使用
  • HibernateTransactionManager : 使用Hibernate進行持久化數據時使用

Spring的回滾方式

Spring事務的默認回滾方式是 : 發生運行時異常回滾

事務定義接口

返回目錄
事務定義接口TransactionDefinition中定義了事務描述相關的三類常量:事務隔離級別事務傳播行爲事務默認超時時限,以及對他們的操作。

事務的四種隔離級別

  • default : 採用DB默認的事務隔離級別,MySQL默認爲REPEATABLE_READ;Oracle默認爲READ_COMMITTED
  • READ_UNCOMMITTED:讀未提交。未解決任何併發問題
  • READ_COMMITTED:讀已提交。解決髒讀,存在不可重複讀與幻讀
  • ERPEATABLE_READ:可重複讀。解決髒讀,不可重複讀,存在幻讀
  • SERIALIZABLE:串行化。不存在並行問題

幾種讀問題的區別

返回目錄

  • 髒讀:修改數據的事務提交失敗發生回滾
    務A 執行A.a() 事務B 執行B.b()
    TbUser TbUser
    讀取到TbUser.id=1 -
    - 修改了id=2
    再次讀取id=2 -
    - 修改提交失敗,回滾爲id=1
    由此事務A手裏的id=2的數據就是髒數據
  • 不可重複讀:修改數據的事務提交成功後,另一個事務無法重複讀取相同數據
    務A 執行A.a() 事務B 執行A.a()
    TbUser TbUser
    讀取TbUser.id=1 修改TbUser.id=2
    - 提交修改
    再次讀取TbUser.id=1的數據 -
    此時如果事務A再次讀取TbUser.id=1的數據就會讀不到
  • 幻讀:數據的條目數發生變化
    務A 執行A.a() 事務B 執行B.b()
    TbUser TbUser
    讀取n條數據 -
    - add一條數據,數據量n+1
    此時事務A的數據就是幻讀,即讀取到的數據已經和實際數據量不同

事務的七種傳播行爲

返回目錄
事務的傳播是指處於不同事務中的方法互相調用時, 方法執行期間事務的維護情況。比如,事務A中的方法a()調用事務B中的方法b(),在調用期間事務的維護情況,就稱爲事務傳播行爲。即事務傳播行爲是加在方法上的
假如事務A的執行過程是:A.a(){ B.b()},事務B的執行過程是:B.b(),那麼傳播行爲執行事務A時,方法B.b()是在事務A中執行還是在事務B中執行。以下舉例都以此事務A的執行過程爲假設前提。

  • REQUIRED : 指定的方法必須在事務內執行.若當前存在事務,就加入到當前事務中;若當前沒有事務,則創建一個事務.這種事務最常見,也是Spring的默認傳播行爲
    過程 : 假如B.b()方法是REQUIRED,如果A.a()有事務,則執行B.b(),如果沒有事務,則爲B.b()創建一個事務執行
  • SUPPORTS : 指定的方法支持當前事務,但若當前沒有事務,則也可以以非事務執行
    過程: 無論A.a()是否有事務,B.b()都可以繼續執行
  • MANDATORY:指定的方法必須在當前事務內執行,若當前沒有事務,則拋出異常
    過程: 如果A.a()有事務,則B.b()正常執行,否則B.b()拋出異常
  • REQUIRES_NEW:總是新建一個事務,若當前事務存在,就將當前事務掛起,直到新事務執行完成
    過程: 如果A.a()有事務,則掛起,併爲B.b()創建一個新事務C,直到新事務C執行完成,才繼續執行A.a()事務。如果A.a()沒有事務,也爲B.b()創建一個新事務
  • NOT_SUPPORTED:指定的方法不能在事務環境中執行,如果當前存在事務,就將事務掛起
    過程: 如果A.a()有事務,則掛起,直到B.b()執行完成後,繼續執行A.a()的事務
  • NEVER:指定的方法不能在事務環境下執行,若當前存在事務,直接拋出異常
    過程: 如果A.a()有事務,則B.b()拋出異常
  • NESTED:指定的方法必須在事務內執行,無論當前是否存在事務,都創建一個事務執行B.b(),但是A.a()的事務不掛起
    **過程:**無論A.a()是否有事務,都爲B.b()創建一個事務C,事務C在事務A中執行

事務管理示例

用AspectJ的AOP管理事務

依賴

返回目錄

<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.1.9.RELEASE</version>
</dependency>

配置spring-context.xml

返回目錄
Spring的配置文件中添加如下設置:

<beans xmlns:tx="http://www.springframework.org/schema/tx">
	<!-- 配置事務管理器 -->
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<!-- 數據源:在spring-context-druid.xml的配置中定義,這裏引用 -->
		<property name="dataSource" ref="dataSource"/>
	</bean>
	
	<!-- 配置事務通知 -->
	<tx:advice id="myAdvice" transaction-manager="transactionManager">
		<tx:attributes>
			<!-- transactionManager事務管理器中以save開頭的方法傳播行爲都是REQUIRED -->
			<tx:method name="save*" propagation="REQUIRED"/>
			<!-- 設置以select和get開頭的方法都不需要事務管理 -->
			<tx:method name="select*" propagation="NEVER" />
			<tx:method name="get*" propagation="NEVER" />
		</tx:attributes>
	</tx:advice>
	
	<!-- 配置顧問和切入點 -->
	<aop:config>
		<!-- 切入點:表達式表示要在哪些方法上加入該切入點, 這裏是com.hello.spring.aspectj.service下的所有方法 -->
		<aop:pointcut id="myPointcut" expression="exection(* com.hello.spring.aspectj.service.*.*(..))" />
		<!-- 配置事務顧問:把哪個事務加到哪個切入點 -->
		<aop:advisor advice-ref="myAdvice" pointcut-ref="myPointcut" />
	</aop:config>
</beans>

使用註解管理事務

使用Spring註解管理事務

返回目錄
通過@Transactional註解方式, 也可將事務植入到相應方法中, 使用註解的方式, 只需要在spring-context.xml配置文件中加入一個tx標籤, 告訴Spring使用註解來完成事務的植入. 該標籤只需指定一個屬性, 即事務管理器

<beans>
	<!-- 配置事務管理器 -->
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<!-- 數據源:在spring-context-druid.xml的配置中定義,這裏引用 -->
		<property name="dataSource" ref="dataSource"/>
	</bean>
	<!-- 開啓事務註解驅動 -->
	<tx:annotation-driven transaction-manager="transactionmanager"/>
</beans>

@Transactional 註解簡介

返回目錄
所有可選屬性:

  • propagation : 用於設置事務傳播屬性. 該屬性類型爲Propagation枚舉, 默認值是Propagation.REQUIRED
  • isolation : 用於設置事務的隔離級別. 該屬性類型爲Isolation枚舉, 默認值是Isolation.DEFAULT
  • readOnly : 用於設置該方法對數據庫的操作是否是隻讀的. 該屬性爲boolean, 默認值爲false
  • timeout : 用於設置本操作與數據庫連接的超時時限. 單位爲秒, 類型爲int, 默認值爲-1, 即沒有時限
  • rollbackFor : 指定需要回滾的異常類. 類型爲Class[], 默認值爲空數組. 如果只有一個異常類, 可以不用數組
  • rollbackForClassName : 指定需要回滾的異常類類名. 類型爲String[], 默認值爲空數組.
  • noRollbackFor : 指定不需要回滾的異常類. 類型爲Class[],默認值爲空數組
  • noRollbackForClassName : 指定不需要回滾的異常類的類名. 類型爲String[], 默認值爲空數組

注意

  1. @Transactional如果用在方法上, 則只能用於public方法, 對於其他非public的方法, 即時加了該註解也不會生效, 因爲Spring會忽略所有非public方法上的@Transactional註解
  2. 如果該註解用在類上, 就是對類內所有public方法生效

測試事務

返回目錄
如果直接運行單元測試的事務測試方法, 會把測試事務的數據直接插入到數據庫中, 造成數據庫的髒數據
解決 在單元測試類上加註解@Transaction@Rollback, 則測試的數據不會實際插入數據庫, 從而不會對實際數據庫造成影響

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章