我猜大概50%的java程式員(别問我怎麼知道的,反正我就是,太失敗了!!!)現在僅僅局限于一個@transactional注解或者在xml中配置事務相關的東西,然後除了事務級别之外,其他的事務知識可能是空白的。為了更加全面地學習,是以我就彙總一下spring事務的知識點,有什麼不對或者補充的,大家記得留言告訴我哈。
關于事務的由來,我就不舉例子了,很多人第一反應就是去銀行存錢(然而我是用花呗的)的操作了。
事務的四大特性acid:
原子性(atomicity)
一緻性(consistency)
隔離性(isolation)
持久性(durability)
(1)read uncommited:是最低的事務隔離級别,它允許另外一個事務可以看到這個事務未送出的資料。
(2)read commited:保證一個事物送出後才能被另外一個事務讀取。另外一個事務不能讀取該事物未送出的資料。
(3)repeatable read:這種事務隔離級别可以防止髒讀,不可重複讀。但是可能會出現幻象讀。它除了保證一個事務不能被另外一個事務讀取未送出的資料之外還避免了以下情況産生(不可重複讀)。
(4)serializable:這是花費最高代價但最可靠的事務隔離級别。事務被處理為順序執行。除了防止髒讀,不可重複讀之外,還避免了幻象讀
說明:
a.髒讀:指當一個事務正字通路資料,并且對資料進行了修改,而這種資料還沒有送出到資料庫中,這時,另外一個事務也通路這個資料,然後使用了這個資料。因為這個資料還沒有送出那麼另外一個事務讀取到的這個資料我們稱之為髒資料。依據髒資料所做的操作肯能是不正确的。
b.不可重複讀:指在一個事務内,多次讀同一資料。在這個事務還沒有執行結束,另外一個事務也通路該同一資料,那麼在第一個事務中的兩次讀取資料之間,由于第二個事務的修改第一個事務兩次讀到的資料可能是不一樣的,這樣就發生了在一個事物内兩次連續讀到的資料是不一樣的,這種情況被稱為是不可重複讀。
c.幻象讀:一個事務先後讀取一個範圍的記錄,但兩次讀取的紀錄數不同,我們稱之為幻象讀(兩次執行同一條 select 語句會出現不同的結果,第二次讀會增加一資料行,并沒有說這兩次執行是在同一個事務中)
@transactional注解估計大家都了解,那麼我們先跟蹤一下它的源碼,發現了platformtransactionmanager這個接口類,具體的接口方法如下:
public interface platformtransactionmanager()...{ // 由transactiondefinition得到transactionstatus對象 transactionstatus gettransaction(transactiondefinition definition) throws transactionexception; // 送出 void commit(transactionstatus status) throws transactionexception; // 復原 void rollback(transactionstatus status) throws transactionexception; }
它就是定義了我們平時操作事務的三大步驟。具體實作由它的子類來實作,也就是如下圖所示的關系:
(1)程式設計式事務管理對基于 pojo 的應用來說是唯一選擇。我們需要在代碼中調用begintransaction()、commit()、rollback()等事務管理相關的方法,這就是程式設計式事務管理。(學過java都會的吧,我就不啰嗦這個了。) (2)基于 transactionproxyfactorybean的聲明式事務管理 (3)基于 @transactional 的聲明式事務管理 (4)基于aspectj aop配置事務
具體實作如下:
import org.springframework.transaction.platformtransactionmanager; import org.springframework.transaction.transactionstatus; import org.springframework.transaction.support.defaulttransactiondefinition; //1、注入事務管理器對象 @autowired private platformtransactionmanager txmanager; //2、開啟事務 transactionstatus status = txmanager.gettransaction(new defaulttransactiondefinition()); //3、送出 txmanager.commit(status); 4、復原 txmanager.rollback(status);
使用場景:在springboot項目開發中,涉及到調用第三方接口,請求第三方接口成功但傳回相關交易失敗的話,需要删除插入表的某條資料,或更新别表中的表狀态同時記錄日志等,将第三方請求的實際完成情況傳回給前端。
配置檔案:applicationcontext.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:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemalocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <!-- 引用外部檔案 db.properties讀取資料庫配置--> <context:property-placeholder location="classpath:db.properties"/> <!-- schemalocation後面兩個命名空間是掃描該包必須有的 --> <!-- 掃描com.sunline包以及所有子包,為所有加了注解的類建立bean --> <context:component-scan base-package="com.sunline"> </context:component-scan> <bean id="datasource" class="org.apache.commons.dbcp.basicdatasource"> <property name="driverclassname" value="${driverclassname}"> </property> <property name="url" value="${url}"> <property name="username" value="${username}"></property> <property name="password" value="${password}"></property> </bean> <bean id="sessionfactory" class="org.springframework.orm.hibernate3.localsessionfactorybean"> <property name="datasource"> <ref bean="datasource" /> <property name="hibernateproperties"> <props> <prop key="hibernate.dialect"> org.hibernate.dialect.mysqldialect </prop> <prop key="dialect"> <prop key="hibernate.hbm2ddl.auto">true</prop> <prop key="hibernate.show_sql">true</prop> </props> <property name="mappingresources"> <list> <value>com/sunline/entity/account.hbm.xml</value> </list> </bean> <!-- 配置hibernate事務管理器 --> <bean id="transactionmanager" class="org.springframework.orm.hibernate3.hibernatetransactionmanager"> <property name="sessionfactory" ref="sessionfactory" /> <!-- ==================================2.使用xml配置聲明式的事務管理(原始方式)=============================================== --> <!-- 配置業務層的代理 --> <bean id="accountserviceproxy" class="org.springframework.transaction.interceptor.transactionproxyfactorybean"> <!-- 配置目标對象 --> <property name="target" ref="accountbiztwo" /> <!-- 注入事務管理器 --> <property name="transactionmanager" ref="transactionmanager"></property> <!-- 注入事務的屬性 --> <property name="transactionattributes"> <!-- prop的格式: * propagation :事務的傳播行為 * isotation :事務的隔離級别 * readonly :隻讀 * -exception :發生哪些異常復原事務 * +exception :發生哪些異常不復原事務 --> <prop key="transfer">propagation_required,readonly</prop> <!-- <prop key="transfer">propagation_required,readonly</prop> --> <!-- <prop key="transfer">propagation_required,+java.lang.arithmeticexception</prop> --> </beans>
這個最簡單,就暫時不細講。
1)、建立工具類,用于開啟事務,送出事務,會滾事務
import org.springframework.beans.factory.annotation.autowired; import org.springframework.context.annotation.scope; import org.springframework.jdbc.datasource.datasourcetransactionmanager; import org.springframework.stereotype.component; import org.springframework.transaction.interceptor.defaulttransactionattribute; //注入spring容器中 @component //建立為多例對象,放置多線程安全問題 @scope("prototype") public class aopaspectutil { @autowired private datasourcetransactionmanager manager; //注入spring的事務管理器 //事務攔截器 private transactionstatus transaction; public transactionstatus begin() { transaction = manager.gettransaction(new defaulttransactionattribute());//設定為預設事務隔離級别 //傳回事務攔截器 return transaction; public void commit() { // 送出事務 manager.commit(transaction); public void rollback() { //復原事務 manager.rollback(transaction); }
2)、定義注解
import java.lang.annotation.elementtype; import java.lang.annotation.retention; import java.lang.annotation.retentionpolicy; import java.lang.annotation.target; @target({elementtype.type,elementtype.method})//設定注解使用範圍 @retention(retentionpolicy.runtime)//注解不僅被儲存到class檔案中,jvm加載class檔案之後,仍然存在 public @interface mytransactional {
3)、自定義通知
使用注意,在針對事務管理方面,方法中不要去try{}catch(){},使用try,,catch後aop不能擷取到異常資訊,會導緻出現異常後不能進行復原,如果确實需要try,,,catch 可以再finally中加上transactionaspectsupport.currenttransactionstatus().setrollbackonly();由此段代碼進行事務的復原
import com.zbin.aop.mytransation.transactionutils; import org.aspectj.lang.proceedingjoinpoint; import org.aspectj.lang.annotation.afterthrowing; import org.aspectj.lang.annotation.around; import org.aspectj.lang.annotation.aspect; import org.springframework.transaction.interceptor.transactionaspectsupport; @aspect public class aoptransaction { private transactionutils transactionutils; @around("execution(* cn.xbmchina.service.userservice.add(..))") public void around(proceedingjoinpoint proceedingjoinpoint) throws throwable { //調用方法之前執行 system.out.println("開啟事務"); transactionutils.begin(); proceedingjoinpoint.proceed(); //調用方法之後執行 system.out.println("送出事務"); transactionutils.commit(); @afterthrowing("execution(* cn.xbmchina.aop.service.userservice.add(..))") public void afterthrowing() { system.out.println("異常通知 "); //擷取目前事務進行復原 //transactionaspectsupport.currenttransactionstatus().setrollbackonly(); transactionutils.rollback();
4)、測試
@mytransactional public void add() { userdao.add("test001", 20); int i = 1 / 0; system.out.println("---------------------"); userdao.add("test002", 20);
事務傳播行為(propagation behavior)指的就是當一個事務方法被另一個事務方法調用時,這個事務方法應該如何進行。
例如:methoda事務方法調用methodb事務方法時,methodb是繼續在調用者methoda的事務中運作呢,還是為自己開啟一個新事務運作,這就是由methodb的事務傳播行為決定的。
看完還是覺得有點懵,那就一個個地為各位簡單介紹一下呗。
如果存在一個事務,則支援目前事務。如果沒有事務則開啟一個新的事務。可以把事務想像成一個膠囊,在這個場景下方法b用的是方法a産生的膠囊(事務)。
@transactional(propagation = propagation.required) public void methoda() { methodb(); // do something public void methodb() { // do something
單獨調用methodb方法時,因為目前上下文不存在事務,是以會開啟一個新的事務。調用methoda方法時,因為目前上下文不存在事務,是以會開啟一個新的事務。當執行到methodb時,methodb發現目前上下文有事務,是以就加入到目前事務中來。
如果存在一個事務,支援目前事務。如果沒有事務,則非事務的執行。但是對于事務同步的事務管理器,propagation_supports與不使用事務有少許不同。
// 事務屬性為supports @transactional(propagation = propagation.supports)
單純的調用methodb時,methodb方法是非事務的執行的。當調用methda時,methodb則加入了methoda的事務中,事務地執行。
如果已經存在一個事務,支援目前事務。如果沒有一個活動的事務,則抛出異常。
// 事務屬性為mandatory @transactional(propagation = propagation.mandatory)
當單獨調用methodb時,因為目前沒有一個活動的事務,則會抛出異常throw new illegaltransactionstateexception(“transaction propagation ‘mandatory’ but no existing transaction found”);當調用methoda時,methodb則加入到methoda的事務中,事務地執行。
使用propagation_requires_new,需要使用 jtatransactionmanager作為事務管理器。它會開啟一個新的事務。如果一個事務已經存在,則先将這個存在的事務挂起。
dosomethinga(); methodb(); dosomethingb(); // do something else // 事務屬性為requires_new @transactional(propagation = propagation.requires_new)
當調用methoda();時,相當于如下代碼
main(){ transactionmanager tm = null; try{ //獲得一個jta事務管理器 tm = gettransactionmanager(); tm.begin();//開啟一個新的事務 transaction ts1 = tm.gettransaction(); dosomething(); tm.suspend();//挂起目前事務 try{ tm.begin();//重新開啟第二個事務 transaction ts2 = tm.gettransaction(); methodb(); ts2.commit();//送出第二個事務 } catch(runtimeexception ex) { ts2.rollback();//復原第二個事務 } finally { //釋放資源 } //methodb執行完後,恢複第一個事務 tm.resume(ts1); dosomethingb(); ts1.commit();//送出第一個事務 } catch(runtimeexception ex) { ts1.rollback();//復原第一個事務 } finally { //釋放資源
在這裡,我把ts1稱為外層事務,ts2稱為内層事務。從上面的代碼可以看出,ts2與ts1是兩個獨立的事務,互不相幹。ts2是否成功并不依賴于 ts1。如果methoda方法在調用methodb方法後的dosomethingb方法失敗了,而methodb方法所做的結果依然被送出。而除了 methodb之外的其它代碼導緻的結果卻被復原了
propagation_not_supported 總是非事務地執行,并挂起任何存在的事務。使用propagation_not_supported,也需要使用jtatransactionmanager作為事務管理器。
總是非事務地執行,如果存在一個活動事務,則抛出異常。
示例:
methoda(){ dosomethinga(); methodb(); dosomethingb(); @transactional(propagation = propagation.newsted) methodb(){ ……
如果單獨調用methodb方法,則按required屬性執行。如果調用methoda方法,相當于下面的效果:
connection con = null; savepoint savepoint = null; con = getconnection(); con.setautocommit(false); dosomethinga(); savepoint = con2.setsavepoint(); con.rollback(savepoint); con.commit(); con.rollback();
當methodb方法調用之前,調用setsavepoint方法,儲存目前的狀态到savepoint。如果methodb方法調用失敗,則恢複到之前儲存的狀态。但是需要注意的是,這時的事務并沒有進行送出,如果後續的代碼(dosomethingb()方法)調用失敗,則復原包括methodb方法的所有操作。嵌套事務一個非常重要的概念就是内層事務依賴于外層事務。外層事務失敗時,會復原内層事務所做的動作。而内層事務操作失敗并不會引起外層事務的復原。
特别地:propagation_requires_new 和 propagation_nested 的最大差別在于, propagation_requires_new 完全是一個新的事務, 而 propagation_nested 則是外部事務的子事務, 如果外部事務 commit, 嵌套事務也會被 commit, 這個規則同樣适用于 roll back.
以上都是一個資料源的情況下的事務處理,那你有沒有想過如果多個資料源的情況下,這個事務如何得到保證呢?還請留意下次更新【spring多資料源事務】