1. 概念
什么是事務(wù)?事務(wù)指的是邏輯上的一組操作泵督,這組操作要么全部成功皮迟,要么全部失敗。
事務(wù)包括四大特性(ACID):原子性(Atomicity)辅搬、一致性(Consistency)唯沮、隔離性(Isolation)、持久性(Durability)
- 原子性:指事務(wù)是一個不可分割的工作單位堪遂,事務(wù)中的操作要么都發(fā)生介蛉,要么都不發(fā)生。
- 一致性:指事務(wù)前后數(shù)據(jù)的完整性必須保持一致溶褪。
- 隔離性:指多個用戶并發(fā)訪問數(shù)據(jù)庫時币旧,一個用戶的事務(wù)不能被其他用戶的事務(wù)所干擾,多個并發(fā)事務(wù)之間的數(shù)據(jù)要相互隔離猿妈。
- 持久性:指一個事務(wù)一旦被提交吹菱,它對數(shù)據(jù)庫中的數(shù)據(jù)的改變就是永久的,即使數(shù)據(jù)庫發(fā)生故障也不應(yīng)該對其有任何影響彭则。
2. 事務(wù)的API
2.1 接口介紹
Spring對事務(wù)管理提供了接口支持鳍刷,主要包括3個高層抽象的接口:
-
PlatformTransactionManager
事務(wù)管理器 -
TransactionDefinition
事務(wù)定義信息(隔離、傳播俯抖、超時输瓜、只讀) -
TransactionStatus
事務(wù)具體的運(yùn)行狀態(tài)信息(是否新事務(wù)、是否有保存點。前痘。凛捏。)
2.2 PlatformTransactionManager接口
Spring為不同的持久化框架提供了不同的PlatformTransactionManager
接口實現(xiàn):
具體實現(xiàn) | 說明 |
---|---|
org.springframework.jdbc.datasource.DataSourceTransactionManager |
使用Spring JDBC或Mybatis進(jìn)行持久化數(shù)據(jù)是使用 |
org.springframework.orm.hibernate5.HibernateTransactionManager |
使用Hibernate5.x版本進(jìn)行持久化數(shù)據(jù)時使用 |
org.springframework.orm.jpa.JpaTransactionManager |
使用JPA進(jìn)行持久化時使用 |
org.springframework.orm.jdo.JdoTransactionManager |
當(dāng)持久化機(jī)制是Jdo時使用 |
org.springframework.transaction.jta.JtaTransactionManager |
使用一個JTA實現(xiàn)來管理事務(wù),在一個事務(wù)跨越多個資源時必須使用 |
2.3 TransactionDefinition接口
TransactionDefinition接口中主要定義了事務(wù)的傳播行為getPropagationBehavior()
芹缔、事務(wù)的隔離級別getIsolationLevel()
坯癣、超時時間getTimeout()
、是否只讀isReadOnly()
最欠、事務(wù)名稱getName()
2.3.1 TransactionDefinition定義事務(wù)隔離級別
我們知道事務(wù)4個特性中有一個隔離性示罗,如果不考慮隔離性的的話,會引發(fā)一些安全問題:臟讀芝硬、不可重復(fù)讀蚜点、幻讀
- 臟讀:一個事務(wù)讀取了另一個事務(wù)改寫但還未提交的數(shù)據(jù),如果這些數(shù)據(jù)被回滾拌阴,則讀到的數(shù)據(jù)是無效的绍绘。
- 不可重復(fù)讀:在同一事務(wù)中,多次讀取同一數(shù)據(jù)返回的結(jié)果有所不同
- 幻讀:一個事務(wù)讀取了幾行記錄后迟赃,另一個事務(wù)插入一些記錄陪拘,幻讀就發(fā)生了。在后來的查詢中纤壁,第一個事務(wù)就會發(fā)現(xiàn)有些原來沒有的記錄
隔離級別就是用來解決上述各種問題的左刽,隔離級別有4種(上圖中的ISOLATION_***
除了ISOLATION_DEFAULT
):
隔離級別 | 含義 |
---|---|
DEFAULT |
使用后端數(shù)據(jù)庫默認(rèn)的隔離級別(Spring中的選擇項) |
READ_UNCOMMITTED |
允許你讀取還未提交的改變了的數(shù)據(jù)∽妹剑可能導(dǎo)致臟欠痴、幻、不可重復(fù)讀 |
READ_COMMITTED |
允許在并發(fā)事務(wù)已經(jīng)提交后讀取秒咨±桑可防止臟讀,但幻讀拭荤、不可重復(fù)讀仍可發(fā)生 |
REPEATABLE_READ |
對相同的字段的多次讀取是一致的茵臭,除非數(shù)據(jù)被事務(wù)本身改變【耸溃可防止臟旦委、不可重復(fù)讀,但幻讀仍可能發(fā)生 |
SERIALIZABLE |
完全服從ACID的隔離級別雏亚,確保不發(fā)送臟缨硝、幻、不可重復(fù)讀罢低。這仔所有的隔離級別中是最慢的查辩,它是典型的通過完全鎖定在事務(wù)中涉及的數(shù)據(jù)表完成的胖笛。 |
2.3.2 TransactionDefinition定義事務(wù)傳播行為
事務(wù)的傳播行為解決的是業(yè)務(wù)層方法之間調(diào)用的時事務(wù)的傳遞問題。
一般我們的系統(tǒng)會分為3層:Web層宜岛、業(yè)務(wù)層Service长踊、持久層DAO;假設(shè)有兩個業(yè)務(wù)類ServiceA
和ServiceB
萍倡,ServiceA
中有方法aaa()
,ServiceB
中有方法bbb()
,有一個業(yè)務(wù)邏輯需要調(diào)用ServiceA.aaa()
和ServiceB.bbb()
才能完成身弊,現(xiàn)在aaa()
方法里有事務(wù),bbb()
方法里也有事務(wù)列敲,那到底要用哪個呢阱佛,這就涉及到了事務(wù)的傳播行為。
事務(wù)的傳播行為有7種(上圖中的PROPAGATION_***
):
隔離級別 | 含義 |
---|---|
PROPAGATION_REQUIRED |
支持當(dāng)前事務(wù)戴而,如果不存在凑术,就新建一個 |
PROPAGATION_SUPPORTS |
支持當(dāng)前事務(wù),如果不存在所意,就不使用事務(wù) |
PROPAGATION_MANDATORY |
支持當(dāng)前事務(wù)淮逊,如果不存在,拋出異常 |
PROPAGATION_REQUIRES_NEW |
如果有事務(wù)存在扶踊,掛起當(dāng)前事務(wù)壮莹,創(chuàng)建一個新的事務(wù) |
PROPAGATION_NOT_SUPPORTED |
以非事務(wù)的方式運(yùn)行,如果有事務(wù)存在姻檀,掛起當(dāng)前事務(wù) |
PROPAGATION_NEVER |
以非事務(wù)的方式運(yùn)行,如果有事務(wù)存在涝滴,拋出異常 |
PROPAGATION_NESTED |
如果當(dāng)前事務(wù)存在绣版,則嵌套事務(wù)執(zhí)行 |
2.4 TransactionStatus接口
事務(wù)本身會存在一些狀態(tài)信息,而TransactionStatus接口里面提供了一些方法歼疮,通過這些方法可以獲得事務(wù)相應(yīng)的狀態(tài)杂抽。
isNewTransaction()
:判斷是否是一個新的事務(wù)
hasSavepoint()
:是否存在保存點
setRollbackOnly()
:設(shè)置為只回滾
isRollbackOnly()
:是否為只回滾
isCompleted()
:是否已完成
3. 編程式事務(wù)管理(不常用)
編程式事務(wù)管理使用TransactionTemplate
模板來控制事務(wù)。TransactionTemplate
的重要方法就是 execute
方法韩脏,此方法調(diào)用 TransactionCallback
進(jìn)行處理缩麸。實際上我們需要處理的事情全部都是在TransactionCallback
中編碼的,我們可以定義一個類并實現(xiàn)此接口赡矢,然后作為 TransactionTemplate.execute
的參數(shù)杭朱。把需要完成的事情放到doInTransaction
中,并且傳入一個 TransactionStatus
參數(shù)吹散,此參數(shù)是來調(diào)用回滾的弧械。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" />
<!-- 數(shù)據(jù)源配置, 使用 Druid 數(shù)據(jù)庫連接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<!-- 數(shù)據(jù)源驅(qū)動類可不寫空民,Druid默認(rèn)會自動根據(jù)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.編程式的事務(wù)管理=============================================== -->
<!-- 定義事務(wù)管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 配置事務(wù)管理的模板:Spring為了簡化事務(wù)管理的代碼而提供的類 -->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"/>
</bean>
</beans>
模擬業(yè)務(wù)方法AccountServiceImpl.transfer
:
/**
* 模擬轉(zhuǎn)賬操作
* @param out :轉(zhuǎn)出賬號
* @param in :轉(zhuǎn)入賬號
* @param money :轉(zhuǎn)賬金額
*/
public void transfer(final String out,final String in,final Double money) {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
try { //具體業(yè)務(wù)代碼
accountDaoImpl.outMoney(out,money); //out賬戶出賬
//int i = 1/0;
accountDaoImpl.inMoney(in,money);// in賬戶入賬
}catch (Exception e){
status.setRollbackOnly(); //設(shè)置回滾
e.printStackTrace();
}
}
});
}
4. 聲明式事務(wù)管理
4.1 聲明式事務(wù)管理方式一:基于TransactionProxyFactoryBean的方式
spring配置文件springDemo2.xml
關(guān)鍵配置:
<!-- ==================================2.使用XML配置聲明式的事務(wù)管理(原始方式)=============================================== -->
<!-- 配置事務(wù)管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 配置業(yè)務(wù)層的代理 -->
<bean id="accountServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<!-- 配置目標(biāo)對象 -->
<property name="target" ref="accountServiceImpl" />
<!-- 注入事務(wù)管理器 -->
<property name="transactionManager" ref="transactionManager"></property>
<!-- 注入事務(wù)的屬性 -->
<property name="transactionAttributes">
<props>
<!--
prop的格式:
* PROPAGATION :事務(wù)的傳播行為
* ISOTATION :事務(wù)的隔離級別
* readOnly :只讀
* -EXCEPTION :發(fā)生哪些異常回滾事務(wù)
* +EXCEPTION :發(fā)生哪些異常不回滾事務(wù)
-->
<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>
模擬業(yè)務(wù)方法AccountServiceImpl.transfer
:
public void transfer(final String out,final String in,final Double money) {
accountDaoImpl.outMoney(out,money);
accountDaoImpl.inMoney(in,money);
}
4.2 聲明式事務(wù)管理方式二:基于AspectJ的XML方式
spring配置文件springDemo3.xml
關(guān)鍵配置:
<!-- ==================================3.使用XML配置聲明式的事務(wù)管理,基于tx/aop=============================================== -->
<!-- 配置事務(wù)管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 配置事務(wù)的通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--
propagation :事務(wù)傳播行為
isolation :事務(wù)的隔離級別
read-only :只讀
rollback-for:發(fā)生哪些異郴ⅲ回滾
no-rollback-for :發(fā)生哪些異常不回滾
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>
模擬業(yè)務(wù)方法AccountServiceImpl.transfer
:
public void transfer(final String out,final String in,final Double money) {
accountDaoImpl.outMoney(out,money);
accountDaoImpl.inMoney(in,money);
}
4.3 聲明式事務(wù)管理方式三:基于注解的方式(推薦)
這種方式是基于@Transactional
注解的衔瓮,簡單易用,更清爽抖甘,是推薦的使用方式热鞍。
@Transactional
可以作用于接口、接口方法单山、類以及類方法上碍现。當(dāng)作用于類上時,該類的所有 public
方法將都具有該類型的事務(wù)屬性米奸,同時昼接,我們也可以在方法級別使用該標(biāo)注來覆蓋類級別的定義。
@Transactional
的屬性:
屬性 | 類型 | 描述 |
---|---|---|
value |
String | 可選的限定描述符悴晰,指定使用的事務(wù)管理器 |
propagation |
enum: Propagation | 可選的事務(wù)傳播行為設(shè)置 |
isolation |
enum: Isolation | 可選的事務(wù)隔離級別設(shè)置 |
readOnly |
boolean | 讀寫或只讀事務(wù)慢睡,默認(rèn)讀寫 |
timeout |
int (in seconds granularity) | 事務(wù)超時時間設(shè)置 |
rollbackFor |
Class對象數(shù)組,必須繼承自Throwable | 導(dǎo)致事務(wù)回滾的異常類數(shù)組 |
rollbackForClassName |
類名數(shù)組铡溪,必須繼承自Throwable | 導(dǎo)致事務(wù)回滾的異常類名字?jǐn)?shù)組 |
noRollbackFor |
Class對象數(shù)組漂辐,必須繼承自Throwable | 不會導(dǎo)致事務(wù)回滾的異常類數(shù)組 |
noRollbackForClassName |
類名數(shù)組,必須繼承自Throwable | 不會導(dǎo)致事務(wù)回滾的異常類名字?jǐn)?shù)組 |
spring配置文件springDemo4.xml
關(guān)鍵配置:
<!-- ==================================4.使用注解配置聲明式事務(wù)============================================ -->
<!-- 配置事務(wù)管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 開啟注解事務(wù) -->
<tx:annotation-driven transaction-manager="transactionManager"/>
模擬業(yè)務(wù)方法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