筆記簡述
本學(xué)習(xí)筆記主要是介紹了事務(wù)相關(guān)的基礎(chǔ)知識绒窑,學(xué)習(xí)編程式事務(wù)和聲明式事務(wù)等不同的事務(wù)使用方法。不過現(xiàn)在實際開發(fā)中,越來越多的服務(wù)都是分布式某饰,單純的spring事務(wù)已經(jīng)無法解決數(shù)據(jù)問題了,但還是有必要去了解事務(wù)的知識點善绎。
Spring更多可查看Spring 源碼學(xué)習(xí)
目錄
Spring 事務(wù) 學(xué)習(xí)
1黔漂、事務(wù)
2、Spring 事務(wù)介紹
2.1禀酱、TransactionDefinition 屬性
2.2炬守、事務(wù)傳遞行為
2.3、事務(wù)隔離級別
3剂跟、Spring 事務(wù)使用方法
3.1减途、編程式事務(wù)
3.2、聲明式事務(wù)
3.2.1曹洽、TransactionInterceptor 方法
3.2.2鳍置、TransactionProxyFactoryBean 方法
3.2.3、命名空間 方法
3.2.4送淆、Transactional 注解 方法
4税产、總結(jié)
5、參考鏈接
1偷崩、事務(wù)
事務(wù)是值多個操作單元組成的集合辟拷,這多個操作單元組合在一起成為一個完整的工作單元,在執(zhí)行過程中要么成功阐斜,要么失敗衫冻,如果失敗了則就相當(dāng)于什么都沒有發(fā)生一樣,為了確保數(shù)據(jù)的完整性和一致性智听。就拿常見的取錢的例子吧羽杰,張三在ATM機器上取1000元錢,但是出現(xiàn)意外了到推,銀行成功扣款考赛,但是ATM機器卻因為硬件故障導(dǎo)致出鈔失敗,張三就損失了1000元錢莉测;如果銀行扣款失敗颜骤,但是卻順利取出了錢,銀行就損失了1000元錢捣卤。在現(xiàn)實中這種情況需要絕對被解決忍抽,這就可以使用事務(wù)去解決了八孝,銀行扣款和ATM出鈔分為2個操作單元,只有2個都成功了鸠项,才意味著成功取錢干跛,否則就認為操作失敗,所有的數(shù)據(jù)都回滾到發(fā)生之前祟绊。
事務(wù)一般和數(shù)據(jù)連接綁定在一起使用楼入,在操作數(shù)據(jù)庫時,一般都是1牧抽、3嘉熊、4步,但是加上事務(wù)必須得捕獲異常扬舒,然后進行回滾操作阐肤,也就多了2、5兩步了讲坎。其中回滾主要是使用類似binlog等方式恢復(fù)數(shù)據(jù)
try{
1. con = getConnection(); // 獲取連接
2. con.setAutoCommit(false); // 設(shè)置是否進行自動提交
3. doing.... // 拼接sql
4. con.commit(); // 提交操作
} catch(RuntimeException ex){ // 不一定就是運用時的異常才會被捕獲孕惜,看用戶自定義設(shè)置
5. con.rollback(); //回滾事務(wù)
}
多說一句,在接下來的學(xué)習(xí)中會了解到如今的實際場景中晨炕,其實事務(wù)使用的并不是很多诊赊。事務(wù)其實就類似于對一個數(shù)據(jù)庫連接進行鎖操作一樣(這是自己描述的,并沒有這個具體的說法府瞄,只是為了便于理解),而現(xiàn)在很多服務(wù)都是分布式架構(gòu)的碘箍,必然存在多個數(shù)據(jù)庫連接遵馆,而各個數(shù)據(jù)庫連接之間沒有關(guān)系,鎖住或者控制一個數(shù)據(jù)庫連接無法解決問題丰榴,況且還有更重要的冪等性問題
事務(wù)包含了4個特性货邓,分別是原子性(Atomicity),一致性(Consistency)四濒,隔離性(Isolation)换况,持久性(Durability)
- 原子性(Atomicity):事務(wù)是一個原子操作,由一系列動作組成盗蟆。事務(wù)的原子性確保動作要么全部完成戈二,要么完全不起作用。
- 一致性(Consistency):一旦事務(wù)完成(不管成功還是失斣省)觉吭,系統(tǒng)必須確保它所建模的業(yè)務(wù)處于一致的狀態(tài),而不會是部分完成部分失敗仆邓。在現(xiàn)實中的數(shù)據(jù)不應(yīng)該被破壞鲜滩。
- 隔離性(Isolation):可能有許多事務(wù)會同時處理相同的數(shù)據(jù)伴鳖,因此每個事務(wù)都應(yīng)該與其他事務(wù)隔離開來,防止數(shù)據(jù)損壞徙硅。
- 持久性(Durability):一旦事務(wù)完成榜聂,無論發(fā)生什么系統(tǒng)錯誤,它的結(jié)果都不應(yīng)該受到影響嗓蘑,這樣就能從任何系統(tǒng)崩潰中恢復(fù)過來须肆。通常情況下,事務(wù)的結(jié)果被寫到持久化存儲器中脐往。
2休吠、Spring 事務(wù)介紹
主要介紹下spring中的事務(wù)的基本情況。在spring中必須得由Spring中的某些對象接管原本的數(shù)據(jù)庫連接业簿,然后通過各種方式添加如上偽代碼顯示的2瘤礁、5兩步完成添加事務(wù)的操作。
spring中提供事務(wù)的接口是PlatformTransactionManager接口類梅尤,具體實現(xiàn)有jdbc管理類等柜思,具體如下圖
// 獲得需要的TransactionStatus對象,是一個事物的屬性對象
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
// 提交
void commit(TransactionStatus status) throws TransactionException;
// 回滾
void rollback(TransactionStatus status) throws TransactionException;
2.1、TransactionDefinition 屬性
事務(wù)管理中必然需要管理各個事務(wù)的屬性信息巷燥,而這些都存儲在TransactionDefinition 接口類中
public interface TransactionDefinition {
int getPropagationBehavior();
//返回事務(wù)的傳播行為赡盘。
int getIsolationLevel();
//返回事務(wù)的隔離級別,事務(wù)管理器根據(jù)它來控制另外一個事務(wù)可以看到本事務(wù)內(nèi)的哪些數(shù)據(jù)缰揪。
int getTimeout();
//返回事務(wù)必須在多少秒內(nèi)完成陨享,某些事務(wù)操作可能比較耗時,默認為-1
boolean isReadOnly();
//事務(wù)是否只讀钝腺,事務(wù)管理器能夠根據(jù)這個返回值進行優(yōu)化抛姑,確保事務(wù)是只讀的。
}
2.2艳狐、事務(wù)傳遞行為
事務(wù)傳遞是指定硝,在開始進行事務(wù)處理的時候,已經(jīng)存在了一個上下文毫目,此時該如何執(zhí)行的這么一個過程蔬啡。
- TransactionDefinition.PROPAGATION_REQUIRED:如果當(dāng)前存在事務(wù),則加入該事務(wù)镀虐;如果當(dāng)前沒有事務(wù)箱蟆,則創(chuàng)建一個新的事務(wù)。
- TransactionDefinition.PROPAGATION_REQUIRES_NEW:創(chuàng)建一個新的事務(wù)粉私,如果當(dāng)前存在事務(wù)顽腾,則把當(dāng)前事務(wù)掛起。
- TransactionDefinition.PROPAGATION_SUPPORTS:如果當(dāng)前存在事務(wù),則加入該事務(wù)抄肖;如果當(dāng)前沒有事務(wù)久信,則以非事務(wù)的方式繼續(xù)運行。
- TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事務(wù)方式運行漓摩,如果當(dāng)前存在事務(wù)裙士,則把當(dāng)前事務(wù)掛起。
- TransactionDefinition.PROPAGATION_NEVER:以非事務(wù)方式運行管毙,如果當(dāng)前存在事務(wù)腿椎,則拋出異常。
- TransactionDefinition.PROPAGATION_MANDATORY:如果當(dāng)前存在事務(wù)夭咬,則加入該事務(wù)啃炸;如果當(dāng)前沒有事務(wù),則拋出異常卓舵。
- TransactionDefinition.PROPAGATION_NESTED:如果當(dāng)前存在事務(wù)南用,則創(chuàng)建一個事務(wù)作為當(dāng)前事務(wù)的嵌套事務(wù)來運行; 如果當(dāng)前沒有事務(wù)掏湾,則該取值等價于TransactionDefinition.PROPAGATION_REQUIRED裹虫。
2.3、事務(wù)隔離級別
事務(wù)隔離是指多個事務(wù)之間的隔離程度融击,當(dāng)兩個事務(wù)對數(shù)據(jù)庫的同一條數(shù)據(jù)進行讀寫操作時筑公,就會因為不同等級的隔離,出現(xiàn)不同的情況尊浪,當(dāng)然隔離的程度越大性能消耗的也更多匣屡。
- TransactionDefinition.ISOLATION_DEFAULT:這是默認值,表示使用底層數(shù)據(jù)庫的默認隔離級別拇涤。對大部分數(shù)據(jù)庫而言耸采,通常這值就是TransactionDefinition.ISOLATION_READ_COMMITTED。
- TransactionDefinition.ISOLATION_READ_UNCOMMITTED:該隔離級別表示一個事務(wù)可以讀取另一個事務(wù)修改但還沒有提交的數(shù)據(jù)工育。該級別不能防止臟讀和不可重復(fù)讀,因此很少使用該隔離級別搓彻。
- TransactionDefinition.ISOLATION_READ_COMMITTED:該隔離級別表示一個事務(wù)只能讀取另一個事務(wù)已經(jīng)提交的數(shù)據(jù)如绸。該級別可以防止臟讀,這也是大多數(shù)情況下的推薦值旭贬。
- TransactionDefinition.ISOLATION_REPEATABLE_READ:該隔離級別表示一個事務(wù)在整個過程中可以多次重復(fù)執(zhí) 行某個查詢怔接,并且每次返回的記錄都相同。即使在多次查詢之間有新增的數(shù)據(jù)滿足該查詢稀轨,這些新增的記錄也會被忽略扼脐。該級別可以防止臟讀和不可重復(fù)讀。
- TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事務(wù)依次逐個執(zhí)行,這樣事務(wù)之間就完全不可能產(chǎn)生干擾瓦侮,也就是說艰赞,該級別可以防止臟讀、不可重復(fù)讀以及幻讀肚吏。但是這將嚴重影響程序的性能方妖。通常情況下也不會用到該級別。
3罚攀、Spring 事務(wù)使用方法
編寫一些demo党觅,實踐中如何具體使用事務(wù)達到我們想要的目的(如下例子有參考網(wǎng)上實例,覺得這個例子挺好)
數(shù)據(jù)庫表結(jié)構(gòu)
public class Money {
private Long id;
private String name;
private Long moneyNum;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Long getMoneyNum() {
return moneyNum;
}
public void setMoneyNum(Long moneyNum) {
this.moneyNum = moneyNum;
}
@Override
public String toString() {
return "Money{" +
"id=" + id +
", name='" + name + '\'' +
", moneyNum=" + moneyNum +
'}';
}
}
@Component("moneyDao")
public class MoneyDao {
@Resource
private JdbcTemplate jdbcTemplate;
public void add(Long id, String name, Long moneyNum){
String sql = "insert into test_money(id, name, money) value(?, ?, ?)";
int record = jdbcTemplate.update(sql, id, name, moneyNum);
System.out.println(record);
}
public void update(Long id, Long moneyNum){
String sql = "update test_money set money = ? where id = ?";
int record = jdbcTemplate.update(sql, moneyNum, id);
System.out.println(record);
}
}
<context:component-scan base-package="com.demo.jdbc" />
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="......"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg name="dataSource" ref="dataSource" />
</bean>
3.1斋泄、編程式事務(wù)
編程式代碼也就是硬編碼的形式杯瞻,TransactionTemplate模板類用于簡化事務(wù)管理,事務(wù)管理由模板類定義炫掐,而具體操作需要通過TransactionCallback回調(diào)接口或TransactionCallbackWithoutResult回調(diào)接口指定魁莉,通過調(diào)用模板類的參數(shù)類型為TransactionCallback或TransactionCallbackWithoutResult的execute方法來自動享受事務(wù)管理。
- TransactionCallback 實現(xiàn)doInTransactionWithoutResult方法卒废,里面填充事務(wù)需要管理的代碼沛厨,有返回值
- TransactionCallbackWithoutResult 繼承自TransactionCallback接口,無需返回數(shù)據(jù)
如果有看這兩個代碼摔认,會發(fā)現(xiàn)其實調(diào)用的是同一個地方逆皮,只是無數(shù)據(jù)返回的是null罷了
我們當(dāng)前就選擇無數(shù)據(jù)返回作為例子
<!-- 配置SpringJdbc的事務(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"/>
<!-- 這里還可以設(shè)置事務(wù)隔離的級別屬性-->
</bean>
public void change(){
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
moneyDao.update(1L, 30L);
// 添加70,移到李四上参袱,當(dāng)然這只是例子沒有做查詢減少的操作电谣,直接更新罷了
test();
moneyDao.update(2L, 120L);
}
});
}
/**
* 假設(shè)拋出了錯誤
*/
private void test(){
throw new IndexOutOfBoundsException("demo test");
}
如上代碼所示,其中就有使用到上面說的TransactionTemplate模板類抹蚀,在這里沒有貼出具體的調(diào)用方法剿牺,自行注入執(zhí)行,兩個更新sql代碼操作包裹在doInTransactionWithoutResult方法中环壤。
其中包含了拋出個運行時錯誤晒来,如果沒有事務(wù),那第一個操作會成功郑现,第二個會失敗湃崩,但是這就導(dǎo)致了數(shù)據(jù)錯誤的情況,添加了事務(wù)接箫,則同時成功或者回滾到操作前攒读。
在運行前還需要提一點的是,上一段話說了是
拋出運行時錯誤
辛友,可是如果是sql本身或者事務(wù)具體執(zhí)行的導(dǎo)致的異常
呢薄扁?這個就需要取到TransactionStatus這個對象的數(shù)據(jù),進行rollback操作,具體的原因后續(xù)的源碼分析中會解答的邓梅。
如下圖脱盲,在有運行中拋出運行時錯誤之后,進行了回滾操作震放,并把異常宾毒,數(shù)據(jù)庫的數(shù)據(jù)并沒有更新
如果把拋出異常這步去掉,那么就可以正常更新數(shù)據(jù)了
總結(jié)
這種編程式的事務(wù)方法其實叫做基于 TransactionTemplate 的編程式事務(wù)管理
殿遂,另外還有一種是基于底層 API 的編程式事務(wù)管理
诈铛,他是利用了PlatformTransactionManager、TransactionDefinition 和 TransactionStatus 三個核心接口的API完成對事物的管理墨礁,不過本質(zhì)來說只是表現(xiàn)形式不同而已幢竹,換湯不換藥
3.2、聲明式事務(wù)
在真正的開發(fā)中恩静,如果需要管理的事務(wù)很多焕毫,使用編程式去硬編碼完成,一方面使得代碼耦合度提高了驶乾,另一方面再去修改成本也很大邑飒,維護難度提高了举娩,最好還是無侵入式的方法最好田绑,也就是我們現(xiàn)在所說的聲明式事務(wù),充分的使用spring的AOP功能媳友,具體的AOP學(xué)習(xí)可以看Spring AOP學(xué)習(xí)风科,當(dāng)然了按照spring的套路肯定提供了xml配置和注解兩種方法了撒轮。
3.2.1、TransactionInterceptor 方法
<bean id="transactionInterceptor"
class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributes">
<props>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<bean id="moneyServiceProxy"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="moneyService"/>
<property name="interceptorNames">
<list>
<idref bean="transactionInterceptor"/>
</list>
</property>
</bean>
在獲取bean的時候贼穆,獲取通過ProxyFactoryBean包裝好的moneyServiceProxy题山,其他不需要做任何操作
這里有一點需要主要的是,transactionInterceptor中的transactionAttributes屬性信息故痊,當(dāng)前demo中的key是"*",實際上有各種可配置的顶瞳,具體的配置是傳播行為 [,隔離級別] [愕秫,只讀屬性] [浊仆,超時屬性] [不影響提交的異常] [,導(dǎo)致回滾的異常]
- 傳播行為就是上面所述的傳播行為的屬性豫领,隔離級別也是類似
- 只讀屬性是readOnly字段
- 超時屬性是必須以
TIMEOUT_
開頭,后面跟著一個數(shù)字舔琅,表示事務(wù)允許超時多少的最大表秒數(shù) - 不影響提交的異常是指出現(xiàn)該些異常等恐,事務(wù)正常提交,不會發(fā)生回滾,需要加上
+
课蔬,例如+RuntimeException
- 導(dǎo)致回滾的異常是值出現(xiàn)該些異常囱稽,事務(wù)將會回滾,需要加上
-
二跋,例如-Exception
同樣的key中的表示方法名稱战惊,可以模糊匹配,如果是*則表示所有的函數(shù)都有事務(wù)扎即,例如key="change"吞获,則意味著只有change函數(shù)需要添加事務(wù)
3.2.2、TransactionProxyFactoryBean 方法
上述的TransactionInterceptor雖然實現(xiàn)了無侵入式的方法谚鄙,但是如果需要添加事務(wù)的類過多則就意味著所有的類都必須有這樣的配置各拷,spring提供了一個新的bean TransactionProxyFactoryBean
,不過我個人覺得也沒改善太多闷营,就是把兩個bean的內(nèi)容組合到一起烤黍,減少了一些配置而已。
<bean id="moneyServiceProxy2"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="target" ref="moneyService" />
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributes">
<props>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
在具體使用的使用傻盟,使用moneyServiceProxy2這個bean即可速蕊,如果仔細查看配置的話,確實和TransactionInterceptor沒有太多的區(qū)別
3.2.3娘赴、命名空間 方法
何所謂命名空間呢规哲,就是利用spring本身的包含的各種NamespaceHandler去解析處理各種自定義的xml標(biāo)簽,自動注入各種bean去完成相關(guān)任務(wù)筝闹,在spring中提供了tx以及aop實現(xiàn)該功能
<tx:advice id="transAdvice" transaction-manager="transactionManager">
<!--配置事務(wù)傳播性媳叨,隔離級別以及超時回滾等問題 -->
<tx:attributes>
<tx:method name="*"
propagation="REQUIRED"
rollback-for="Exception"
timeout="10"
read-only="true"
isolation="DEFAULT"
no-rollback-for="Exception"
<!-- 以上都是配置的屬性而已 -->
/>
</tx:attributes>
</tx:advice>
<aop:config>
<!--配置事務(wù)切點 -->
<aop:pointcut id="services"
expression="execution(* com.demo.jdbc.MoneyService.*(..))" />
<aop:advisor pointcut-ref="services" advice-ref="transAdvice" />
</aop:config>
先是設(shè)置了一些包含事務(wù)的方法,并且設(shè)置有傳遞行為关顷、隔離級別等屬性糊秆,然后利用AOP的切面功能去實現(xiàn)事務(wù)處理
3.2.4、Transactional 注解 方法
既然xml配置都已經(jīng)存在了议双,再支持注解的方法也是完全可以的
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
...
可以支持在類上和方法上添加該注解痘番,如果再類上面則該類中的public方法都會被添加上具體的事務(wù)方法的,例如如下配置
@Transactional(propagation = Propagation.REQUIRED)
public void change() {
moneyDao.update(1L, 30L);
test();
moneyDao.update(2L, 120L);
}
還有個點別忘記了平痰,添加了注解還需要添加支持解析該注解的功能汞舱,在xml中添加上
<tx:annotation-driven transaction-manager="transactionManager"/>
4、總結(jié)
主要是介紹了事務(wù)的基本信息以及如何具體的使用事務(wù)宗雇,從本質(zhì)來說上面幾種方法沒有太多的差異昂芜,只是spring提供了更加便捷的方法去實現(xiàn)同樣的功能,后續(xù)會學(xué)習(xí)源碼層面赔蒲,了解spring如何實現(xiàn)該功能的泌神。以及異常如何被捕獲回滾操作良漱、冪等性的問題等還需要解決。