1. 概念
什么是事務?事務指的是邏輯上的一組操作,這組操作要么全部成功,要么全部失敗。
事務包括四大特性(ACID):原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)、持久性(Durability)
- 原子性:指事務是一個不可分割的工作單位,事務中的操作要么都發生,要么都不發生。
- 一致性:指事務前后數據的完整性必須保持一致。
- 隔離性:指多個用戶并發訪問數據庫時,一個用戶的事務不能被其他用戶的事務所干擾,多個并發事務之間的數據要相互隔離。
- 持久性:指一個事務一旦被提交,它對數據庫中的數據的改變就是永久的,即使數據庫發生故障也不應該對其有任何影響。
2. 事務的API
2.1 接口介紹
Spring對事務管理提供了接口支持,主要包括3個高層抽象的接口:
-
PlatformTransactionManager
事務管理器 -
TransactionDefinition
事務定義信息(隔離、傳播、超時、只讀) -
TransactionStatus
事務具體的運行狀態信息(是否新事務、是否有保存點。。。)
2.2 PlatformTransactionManager接口
Spring為不同的持久化框架提供了不同的PlatformTransactionManager
接口實現:
具體實現 | 說明 |
---|---|
org.springframework.jdbc.datasource.DataSourceTransactionManager |
使用Spring JDBC或Mybatis進行持久化數據是使用 |
org.springframework.orm.hibernate5.HibernateTransactionManager |
使用Hibernate5.x版本進行持久化數據時使用 |
org.springframework.orm.jpa.JpaTransactionManager |
使用JPA進行持久化時使用 |
org.springframework.orm.jdo.JdoTransactionManager |
當持久化機制是Jdo時使用 |
org.springframework.transaction.jta.JtaTransactionManager |
使用一個JTA實現來管理事務,在一個事務跨越多個資源時必須使用 |
2.3 TransactionDefinition接口
TransactionDefinition接口中主要定義了事務的傳播行為getPropagationBehavior()
、事務的隔離級別getIsolationLevel()
、超時時間getTimeout()
、是否只讀isReadOnly()
、事務名稱getName()
2.3.1 TransactionDefinition定義事務隔離級別
我們知道事務4個特性中有一個隔離性,如果不考慮隔離性的的話,會引發一些安全問題:臟讀、不可重復讀、幻讀
- 臟讀:一個事務讀取了另一個事務改寫但還未提交的數據,如果這些數據被回滾,則讀到的數據是無效的。
- 不可重復讀:在同一事務中,多次讀取同一數據返回的結果有所不同
- 幻讀:一個事務讀取了幾行記錄后,另一個事務插入一些記錄,幻讀就發生了。在后來的查詢中,第一個事務就會發現有些原來沒有的記錄
隔離級別就是用來解決上述各種問題的,隔離級別有4種(上圖中的ISOLATION_***
除了ISOLATION_DEFAULT
):
隔離級別 | 含義 |
---|---|
DEFAULT |
使用后端數據庫默認的隔離級別(Spring中的選擇項) |
READ_UNCOMMITTED |
允許你讀取還未提交的改變了的數據。可能導致臟、幻、不可重復讀 |
READ_COMMITTED |
允許在并發事務已經提交后讀取。可防止臟讀,但幻讀、不可重復讀仍可發生 |
REPEATABLE_READ |
對相同的字段的多次讀取是一致的,除非數據被事務本身改變。可防止臟、不可重復讀,但幻讀仍可能發生 |
SERIALIZABLE |
完全服從ACID的隔離級別,確保不發送臟、幻、不可重復讀。這仔所有的隔離級別中是最慢的,它是典型的通過完全鎖定在事務中涉及的數據表完成的。 |
2.3.2 TransactionDefinition定義事務傳播行為
事務的傳播行為解決的是業務層方法之間調用的時事務的傳遞問題。
一般我們的系統會分為3層:Web層、業務層Service、持久層DAO;假設有兩個業務類ServiceA
和ServiceB
,ServiceA
中有方法aaa()
,ServiceB
中有方法bbb()
,有一個業務邏輯需要調用ServiceA.aaa()
和ServiceB.bbb()
才能完成,現在aaa()
方法里有事務,bbb()
方法里也有事務,那到底要用哪個呢,這就涉及到了事務的傳播行為。
事務的傳播行為有7種(上圖中的PROPAGATION_***
):
隔離級別 | 含義 |
---|---|
PROPAGATION_REQUIRED |
支持當前事務,如果不存在,就新建一個 |
PROPAGATION_SUPPORTS |
支持當前事務,如果不存在,就不使用事務 |
PROPAGATION_MANDATORY |
支持當前事務,如果不存在,拋出異常 |
PROPAGATION_REQUIRES_NEW |
如果有事務存在,掛起當前事務,創建一個新的事務 |
PROPAGATION_NOT_SUPPORTED |
以非事務的方式運行,如果有事務存在,掛起當前事務 |
PROPAGATION_NEVER |
以非事務的方式運行,如果有事務存在,拋出異常 |
PROPAGATION_NESTED |
如果當前事務存在,則嵌套事務執行 |
2.4 TransactionStatus接口
事務本身會存在一些狀態信息,而TransactionStatus接口里面提供了一些方法,通過這些方法可以獲得事務相應的狀態。
isNewTransaction()
:判斷是否是一個新的事務
hasSavepoint()
:是否存在保存點
setRollbackOnly()
:設置為只回滾
isRollbackOnly()
:是否為只回滾
isCompleted()
:是否已完成
3. 編程式事務管理(不常用)
編程式事務管理使用TransactionTemplate
模板來控制事務。TransactionTemplate
的重要方法就是 execute
方法,此方法調用 TransactionCallback
進行處理。實際上我們需要處理的事情全部都是在TransactionCallback
中編碼的,我們可以定義一個類并實現此接口,然后作為 TransactionTemplate.execute
的參數。把需要完成的事情放到doInTransaction
中,并且傳入一個 TransactionStatus
參數,此參數是來調用回滾的。demo如下:
spring配置文件springDemo1.xml
:
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd"
default-lazy-init="true">
<description>Spring Configuration</description>
<!-- 加載配置屬性文件 -->
<context:property-placeholder ignore-unresolvable="true" location="classpath:jdbc.properties" />
<!-- 使用Annotation自動注冊Bean base-package 如果多個,用“,”分隔-->
<context:component-scan base-package="com.zhoubg.spring.transaction.demo1" />
<!-- 數據源配置, 使用 Druid 數據庫連接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<!-- 數據源驅動類可不寫,Druid默認會自動根據URL識別DriverClass -->
<property name="driverClassName" value="${jdbc.driver}" />
<!-- 基本屬性 url、user、password -->
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- ==================================1.編程式的事務管理=============================================== -->
<!-- 定義事務管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 配置事務管理的模板:Spring為了簡化事務管理的代碼而提供的類 -->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"/>
</bean>
</beans>
模擬業務方法AccountServiceImpl.transfer
:
/**
* 模擬轉賬操作
* @param out :轉出賬號
* @param in :轉入賬號
* @param money :轉賬金額
*/
public void transfer(final String out,final String in,final Double money) {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
try { //具體業務代碼
accountDaoImpl.outMoney(out,money); //out賬戶出賬
//int i = 1/0;
accountDaoImpl.inMoney(in,money);// in賬戶入賬
}catch (Exception e){
status.setRollbackOnly(); //設置回滾
e.printStackTrace();
}
}
});
}
4. 聲明式事務管理
4.1 聲明式事務管理方式一:基于TransactionProxyFactoryBean的方式
spring配置文件springDemo2.xml
關鍵配置:
<!-- ==================================2.使用XML配置聲明式的事務管理(原始方式)=============================================== -->
<!-- 配置事務管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 配置業務層的代理 -->
<bean id="accountServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<!-- 配置目標對象 -->
<property name="target" ref="accountServiceImpl" />
<!-- 注入事務管理器 -->
<property name="transactionManager" ref="transactionManager"></property>
<!-- 注入事務的屬性 -->
<property name="transactionAttributes">
<props>
<!--
prop的格式:
* PROPAGATION :事務的傳播行為
* ISOTATION :事務的隔離級別
* readOnly :只讀
* -EXCEPTION :發生哪些異常回滾事務
* +EXCEPTION :發生哪些異常不回滾事務
-->
<prop key="transfer">PROPAGATION_REQUIRED</prop>
<!-- <prop key="transfer">PROPAGATION_REQUIRED,readOnly</prop> -->
<!-- <prop key="transfer">PROPAGATION_REQUIRED,+java.lang.ArithmeticException</prop> -->
</props>
</property>
</bean>
模擬業務方法AccountServiceImpl.transfer
:
public void transfer(final String out,final String in,final Double money) {
accountDaoImpl.outMoney(out,money);
accountDaoImpl.inMoney(in,money);
}
4.2 聲明式事務管理方式二:基于AspectJ的XML方式
spring配置文件springDemo3.xml
關鍵配置:
<!-- ==================================3.使用XML配置聲明式的事務管理,基于tx/aop=============================================== -->
<!-- 配置事務管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 配置事務的通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--
propagation :事務傳播行為
isolation :事務的隔離級別
read-only :只讀
rollback-for:發生哪些異常回滾
no-rollback-for :發生哪些異常不回滾
timeout :過期信息
-->
<tx:method name="transfer" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>
<!-- 配置切面 -->
<aop:config>
<!-- 配置切入點 -->
<aop:pointcut expression="execution(* com.zhoubg.spring.transaction..demo3.AccountService+.*(..))" id="pointcut1"/>
<!-- 配置切面 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1"/>
</aop:config>
模擬業務方法AccountServiceImpl.transfer
:
public void transfer(final String out,final String in,final Double money) {
accountDaoImpl.outMoney(out,money);
accountDaoImpl.inMoney(in,money);
}
4.3 聲明式事務管理方式三:基于注解的方式(推薦)
這種方式是基于@Transactional
注解的,簡單易用,更清爽,是推薦的使用方式。
@Transactional
可以作用于接口、接口方法、類以及類方法上。當作用于類上時,該類的所有 public
方法將都具有該類型的事務屬性,同時,我們也可以在方法級別使用該標注來覆蓋類級別的定義。
@Transactional
的屬性:
屬性 | 類型 | 描述 |
---|---|---|
value |
String | 可選的限定描述符,指定使用的事務管理器 |
propagation |
enum: Propagation | 可選的事務傳播行為設置 |
isolation |
enum: Isolation | 可選的事務隔離級別設置 |
readOnly |
boolean | 讀寫或只讀事務,默認讀寫 |
timeout |
int (in seconds granularity) | 事務超時時間設置 |
rollbackFor |
Class對象數組,必須繼承自Throwable | 導致事務回滾的異常類數組 |
rollbackForClassName |
類名數組,必須繼承自Throwable | 導致事務回滾的異常類名字數組 |
noRollbackFor |
Class對象數組,必須繼承自Throwable | 不會導致事務回滾的異常類數組 |
noRollbackForClassName |
類名數組,必須繼承自Throwable | 不會導致事務回滾的異常類名字數組 |
spring配置文件springDemo4.xml
關鍵配置:
<!-- ==================================4.使用注解配置聲明式事務============================================ -->
<!-- 配置事務管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 開啟注解事務 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
模擬業務方法AccountServiceImpl.transfer
:
@Transactional(readOnly = false)
public void transfer(final String out,final String in,final Double money) {
accountDaoImpl.outMoney(out,money);
accountDaoImpl.inMoney(in,money);
}
以上示例源碼:learn-spring-transaction