文章目錄
Spring事務管理
事務用於數據庫的訪問,但是一般情況下,需要將事務提升到業務層,即Service層。這樣做是爲了能夠使用事務的特性來管理具體的業務。
在Spring中通常可以通過以下三種方式來實現對事務的管理:
- 使用Spring的事務代理工廠管理事務(已廢棄)
- 使用Spring的事務註解管理事務(目前最常用)
- 使用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 執行A.a() 事務B 執行A.a() TbUser TbUser 讀取TbUser.id=1 修改TbUser.id=2 - 提交修改 再次讀取TbUser.id=1的數據 - - 幻讀:數據的條目數發生變化
務A 執行A.a() 事務B 執行B.b() TbUser TbUser 讀取n條數據 - - add一條數據,數據量n+1
事務的七種傳播行爲
返回目錄
事務的傳播是指處於不同事務中的方法在互相調用時, 方法執行期間事務的維護情況。比如,事務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[]
, 默認值爲空數組
注意
@Transactional
如果用在方法上, 則只能用於public
方法, 對於其他非public
的方法, 即時加了該註解也不會生效, 因爲Spring會忽略所有非public
方法上的@Transactional
註解- 如果該註解用在類上, 就是對類內所有
public
方法生效
測試事務
返回目錄
如果直接運行單元測試的事務測試方法, 會把測試事務的數據直接插入到數據庫中, 造成數據庫的髒數據
解決 在單元測試類上加註解@Transaction
和@Rollback
, 則測試的數據不會實際插入數據庫, 從而不會對實際數據庫造成影響