
前言
我猜大概50%的Java程序員(別問(wèn)我怎么知道的,反正我就是,太失敗了?。。。┈F(xiàn)在僅僅局限于一個(gè)@Transactional注解或者在XML中配置事務(wù)相關(guān)的東西,然后除了事務(wù)級(jí)別之外,其他的事務(wù)知識(shí)可能是空白的。為了更加全面地學(xué)習(xí),所以我就匯總一下Spring事務(wù)的知識(shí)點(diǎn),有什么不對(duì)或者補(bǔ)充的,大家記得留言告訴我哈。
為什么要事務(wù)
關(guān)于事務(wù)的由來(lái),我就不舉例子了,很多人第一反應(yīng)就是去銀行存錢(然而我是用花唄的)的操作了。事務(wù)的四大特性ACID:原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)、持久性(Durability)
事務(wù)的隔離級(jí)別
(1)read uncommited:是最低的事務(wù)隔離級(jí)別,它允許另外一個(gè)事務(wù)可以看到這個(gè)事務(wù)未提交的數(shù)據(jù)。
(2)read commited:保證一個(gè)事物提交后才能被另外一個(gè)事務(wù)讀取。另外一個(gè)事務(wù)不能讀取該事物未提交的數(shù)據(jù)。
(3)repeatable read:這種事務(wù)隔離級(jí)別可以防止臟讀,不可重復(fù)讀。但是可能會(huì)出現(xiàn)幻象讀。它除了保證一個(gè)事務(wù)不能被另外一個(gè)事務(wù)讀取未提交的數(shù)據(jù)之外還避免了以下情況產(chǎn)生(不可重復(fù)讀)。
(4)serializable:這是花費(fèi)最高代價(jià)但最可靠的事務(wù)隔離級(jí)別。事務(wù)被處理為順序執(zhí)行。除了防止臟讀,不可重復(fù)讀之外,還避免了幻象讀
說(shuō)明:
a.臟讀:指當(dāng)一個(gè)事務(wù)正字訪問(wèn)數(shù)據(jù),并且對(duì)數(shù)據(jù)進(jìn)行了修改,而這種數(shù)據(jù)還沒(méi)有提交到數(shù)據(jù)庫(kù)中,這時(shí),另外一個(gè)事務(wù)也訪問(wèn)這個(gè)數(shù)據(jù),然后使用了這個(gè)數(shù)據(jù)。因?yàn)檫@個(gè)數(shù)據(jù)還沒(méi)有提交那么另外一個(gè)事務(wù)讀取到的這個(gè)數(shù)據(jù)我們稱之為臟數(shù)據(jù)。依據(jù)臟數(shù)據(jù)所做的操作肯能是不正確的。
b.不可重復(fù)讀:指在一個(gè)事務(wù)內(nèi),多次讀同一數(shù)據(jù)。在這個(gè)事務(wù)還沒(méi)有執(zhí)行結(jié)束,另外一個(gè)事務(wù)也訪問(wèn)該同一數(shù)據(jù),那么在第一個(gè)事務(wù)中的兩次讀取數(shù)據(jù)之間,由于第二個(gè)事務(wù)的修改第一個(gè)事務(wù)兩次讀到的數(shù)據(jù)可能是不一樣的,這樣就發(fā)生了在一個(gè)事物內(nèi)兩次連續(xù)讀到的數(shù)據(jù)是不一樣的,這種情況被稱為是不可重復(fù)讀。
c.幻象讀:一個(gè)事務(wù)先后讀取一個(gè)范圍的記錄,但兩次讀取的紀(jì)錄數(shù)不同,我們稱之為幻象讀(兩次執(zhí)行同一條 select 語(yǔ)句會(huì)出現(xiàn)不同的結(jié)果,第二次讀會(huì)增加一數(shù)據(jù)行,并沒(méi)有說(shuō)這兩次執(zhí)行是在同一個(gè)事務(wù)中)
接口體系
@Transactional注解估計(jì)大家都了解,那么我們先跟蹤一下它的源碼,發(fā)現(xiàn)了PlatformTransactionManager這個(gè)接口類,具體的接口方法如下:
public interface PlatformTransactionManager()...{
// 由TransactionDefinition得到TransactionStatus對(duì)象
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
// 提交
void commit(TransactionStatus status) throws TransactionException;
// 回滾
void rollback(TransactionStatus status) throws TransactionException;
}
它就是定義了我們平時(shí)操作事務(wù)的三大步驟。具體實(shí)現(xiàn)由它的子類來(lái)實(shí)現(xiàn),也就是如下圖所示的關(guān)系:

事務(wù)幾種實(shí)現(xiàn)方式
(1)編程式事務(wù)管理對(duì)基于 POJO 的應(yīng)用來(lái)說(shuō)是唯一選擇。我們需要在代碼中調(diào)用beginTransaction()、commit()、rollback()等事務(wù)管理相關(guān)的方法,這就是編程式事務(wù)管理。(學(xué)過(guò)Java都會(huì)的吧,我就不啰嗦這個(gè)了。)
(2)基于 TransactionProxyFactoryBean的聲明式事務(wù)管理
(3)基于 @Transactional 的聲明式事務(wù)管理
(4)基于Aspectj AOP配置事務(wù)
1、編程式事務(wù)
具體實(shí)現(xiàn)如下:
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
//1、注入事務(wù)管理器對(duì)象
@Autowired
private PlatformTransactionManager txManager;
//2、開(kāi)啟事務(wù)
TransactionStatus status = txManager.getTransaction(new DefaultTransactionDefinition());
//3、提交
txManager.commit(status);
4、回滾
txManager.rollback(status);
使用場(chǎng)景:
在springboot項(xiàng)目開(kāi)發(fā)中,涉及到調(diào)用第三方接口,請(qǐng)求第三方接口成功但返回相關(guān)交易失敗的話,需要?jiǎng)h除插入表的某條數(shù)據(jù),或更新別表中的表狀態(tài)同時(shí)記錄日志等,將第三方請(qǐng)求的實(shí)際完成情況返回給前端。
2、TransactionProxyFactoryBean實(shí)現(xiàn)事務(wù)
配置文件: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讀取數(shù)據(jù)庫(kù)配置-->
<context:property-placeholder location="classpath:db.properties"/>
<!-- schemaLocation后面兩個(gè)命名空間是掃描該包必須有的 -->
<!-- 掃描com.sunline包以及所有子包,為所有加了注解的類創(chuàng)建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>
<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>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">
org.hibernate.dialect.MySQLDialect
</prop>
<prop key="dialect">
org.hibernate.dialect.MySQLDialect
</prop>
<prop key="hibernate.hbm2ddl.auto">true</prop>
<prop key="hibernate.show_sql">true</prop>
</props>
</property>
<property name="mappingResources">
<list>
<value>com/sunline/entity/Account.hbm.xml</value>
</list>
</property>
</bean>
<!-- 配置Hibernate事務(wù)管理器 -->
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<!-- ==================================2.使用XML配置聲明式的事務(wù)管理(原始方式)=============================================== -->
<!-- 配置業(yè)務(wù)層的代理 -->
<bean id="accountServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<!-- 配置目標(biāo)對(duì)象 -->
<property name="target" ref="accountBizTwo" />
<!-- 注入事務(wù)管理器 -->
<property name="transactionManager" ref="transactionManager"></property>
<!-- 注入事務(wù)的屬性 -->
<property name="transactionAttributes">
<props>
<!--
prop的格式:
* PROPAGATION :事務(wù)的傳播行為
* ISOTATION :事務(wù)的隔離級(jí)別
* readOnly :只讀
* -EXCEPTION :發(fā)生哪些異常回滾事務(wù)
* +EXCEPTION :發(fā)生哪些異常不回滾事務(wù)
-->
<prop key="transfer">PROPAGATION_REQUIRED,readOnly</prop>
<!-- <prop key="transfer">PROPAGATION_REQUIRED,readOnly</prop> -->
<!-- <prop key="transfer">PROPAGATION_REQUIRED,+java.lang.ArithmeticException</prop> -->
</props>
</property>
</bean>
</beans>
這個(gè)主要是使用xml配置事務(wù),其實(shí)跟現(xiàn)在的@Transactional 的效果一樣。
更多詳細(xì)的配置可以參考文章:https://blog.csdn.net/linhaiyun_ytdx/article/details/78236418
3、基于 @Transactional 的聲明式事務(wù)管理
這個(gè)最簡(jiǎn)單,就暫時(shí)不細(xì)講。

4、基于Aspectj AOP配置事務(wù)
1)、創(chuàng)建工具類,用于開(kāi)啟事務(wù),提交事務(wù),會(huì)滾事務(wù)
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.TransactionStatus;
import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
//注入spring容器中
@Component
//創(chuàng)建為多例對(duì)象,放置多線程安全問(wèn)題
@Scope("prototype")
public class AopAspectUtil {
@Autowired
private DataSourceTransactionManager manager; //注入spring的事務(wù)管理器
//事務(wù)攔截器
private TransactionStatus transaction;
public TransactionStatus begin() {
transaction = manager.getTransaction(new DefaultTransactionAttribute());//設(shè)置為默認(rèn)事務(wù)隔離級(jí)別
//返回事務(wù)攔截器
return transaction;
}
public void commit() {
// 提交事務(wù)
manager.commit(transaction);
}
public void rollback() {
//回滾事務(wù)
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})//設(shè)置注解使用范圍
@Retention(RetentionPolicy.RUNTIME)//注解不僅被保存到class文件中,jvm加載class文件之后,仍然存在
public @interface MyTransactional {
}
3)、自定義通知
使用注意,在針對(duì)事務(wù)管理方面,方法中不要去try{}catch(){},使用try,,catch后aop不能獲取到異常信息,會(huì)導(dǎo)致出現(xiàn)異常后不能進(jìn)行回滾,如果確實(shí)需要try,,,catch 可以再finally中加上TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();由此段代碼進(jìn)行事務(wù)的回滾
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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
@Component
@Aspect
public class AopTransaction {
@Autowired
private TransactionUtils transactionUtils;
@Around("execution(* cn.xbmchina.service.UserService.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
//調(diào)用方法之前執(zhí)行
System.out.println("開(kāi)啟事務(wù)");
transactionUtils.begin();
proceedingJoinPoint.proceed();
//調(diào)用方法之后執(zhí)行
System.out.println("提交事務(wù)");
transactionUtils.commit();
}
@AfterThrowing("execution(* cn.xbmchina.aop.service.UserService.add(..))")
public void afterThrowing() {
System.out.println("異常通知 ");
//獲取當(dāng)前事務(wù)進(jìn)行回滾
//TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
transactionUtils.rollBack();
}
}
4)、測(cè)試
@MyTransactional
public void add() {
userDao.add("test001", 20);
int i = 1 / 0;
System.out.println("---------------------");
userDao.add("test002", 20);
}
事務(wù)傳播行為
以下部分摘自「唐大麥」:https://blog.csdn.net/soonfly/article/details/70305683
事務(wù)傳播行為(propagation behavior)指的就是當(dāng)一個(gè)事務(wù)方法被另一個(gè)事務(wù)方法調(diào)用時(shí),這個(gè)事務(wù)方法應(yīng)該如何進(jìn)行。
例如:methodA事務(wù)方法調(diào)用methodB事務(wù)方法時(shí),methodB是繼續(xù)在調(diào)用者methodA的事務(wù)中運(yùn)行呢,還是為自己開(kāi)啟一個(gè)新事務(wù)運(yùn)行,這就是由methodB的事務(wù)傳播行為決定的。

看完還是覺(jué)得有點(diǎn)懵,那就一個(gè)個(gè)地為各位簡(jiǎn)單介紹一下唄。
1、PROPAGATION_REQUIRED
如果存在一個(gè)事務(wù),則支持當(dāng)前事務(wù)。如果沒(méi)有事務(wù)則開(kāi)啟一個(gè)新的事務(wù)。
可以把事務(wù)想像成一個(gè)膠囊,在這個(gè)場(chǎng)景下方法B用的是方法A產(chǎn)生的膠囊(事務(wù))。
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
methodB();
// do something
}
@Transactional(propagation = Propagation.REQUIRED)
public void methodB() {
// do something
}
單獨(dú)調(diào)用methodB方法時(shí),因?yàn)楫?dāng)前上下文不存在事務(wù),所以會(huì)開(kāi)啟一個(gè)新的事務(wù)。
調(diào)用methodA方法時(shí),因?yàn)楫?dāng)前上下文不存在事務(wù),所以會(huì)開(kāi)啟一個(gè)新的事務(wù)。當(dāng)執(zhí)行到methodB時(shí),methodB發(fā)現(xiàn)當(dāng)前上下文有事務(wù),因此就加入到當(dāng)前事務(wù)中來(lái)。
2、PROPAGATION_SUPPORTS
如果存在一個(gè)事務(wù),支持當(dāng)前事務(wù)。如果沒(méi)有事務(wù),則非事務(wù)的執(zhí)行。但是對(duì)于事務(wù)同步的事務(wù)管理器,PROPAGATION_SUPPORTS與不使用事務(wù)有少許不同。
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
methodB();
// do something
}
// 事務(wù)屬性為SUPPORTS
@Transactional(propagation = Propagation.SUPPORTS)
public void methodB() {
// do something
}
單純的調(diào)用methodB時(shí),methodB方法是非事務(wù)的執(zhí)行的。當(dāng)調(diào)用methdA時(shí),methodB則加入了methodA的事務(wù)中,事務(wù)地執(zhí)行。
3、PROPAGATION_MANDATORY
如果已經(jīng)存在一個(gè)事務(wù),支持當(dāng)前事務(wù)。如果沒(méi)有一個(gè)活動(dòng)的事務(wù),則拋出異常。
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
methodB();
// do something
}
// 事務(wù)屬性為MANDATORY
@Transactional(propagation = Propagation.MANDATORY)
public void methodB() {
// do something
}
當(dāng)單獨(dú)調(diào)用methodB時(shí),因?yàn)楫?dāng)前沒(méi)有一個(gè)活動(dòng)的事務(wù),則會(huì)拋出異常throw new IllegalTransactionStateException(“Transaction propagation ‘mandatory’ but no existing transaction found”);當(dāng)調(diào)用methodA時(shí),methodB則加入到methodA的事務(wù)中,事務(wù)地執(zhí)行。
4、PROPAGATION_MANDATORY
使用PROPAGATION_REQUIRES_NEW,需要使用 JtaTransactionManager作為事務(wù)管理器。
它會(huì)開(kāi)啟一個(gè)新的事務(wù)。如果一個(gè)事務(wù)已經(jīng)存在,則先將這個(gè)存在的事務(wù)掛起。
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
doSomeThingA();
methodB();
doSomeThingB();
// do something else
}
// 事務(wù)屬性為REQUIRES_NEW
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
// do something
}
當(dāng)調(diào)用methodA();時(shí),相當(dāng)于如下代碼
main(){
TransactionManager tm = null;
try{
//獲得一個(gè)JTA事務(wù)管理器
tm = getTransactionManager();
tm.begin();//開(kāi)啟一個(gè)新的事務(wù)
Transaction ts1 = tm.getTransaction();
doSomeThing();
tm.suspend();//掛起當(dāng)前事務(wù)
try{
tm.begin();//重新開(kāi)啟第二個(gè)事務(wù)
Transaction ts2 = tm.getTransaction();
methodB();
ts2.commit();//提交第二個(gè)事務(wù)
} Catch(RunTimeException ex) {
ts2.rollback();//回滾第二個(gè)事務(wù)
} finally {
//釋放資源
}
//methodB執(zhí)行完后,恢復(fù)第一個(gè)事務(wù)
tm.resume(ts1);
doSomeThingB();
ts1.commit();//提交第一個(gè)事務(wù)
} catch(RunTimeException ex) {
ts1.rollback();//回滾第一個(gè)事務(wù)
} finally {
//釋放資源
}
}
在這里,我把ts1稱為外層事務(wù),ts2稱為內(nèi)層事務(wù)。從上面的代碼可以看出,ts2與ts1是兩個(gè)獨(dú)立的事務(wù),互不相干。Ts2是否成功并不依賴于 ts1。如果methodA方法在調(diào)用methodB方法后的doSomeThingB方法失敗了,而methodB方法所做的結(jié)果依然被提交。而除了 methodB之外的其它代碼導(dǎo)致的結(jié)果卻被回滾了
5、PROPAGATION_NOT_SUPPORTED
PROPAGATION_NOT_SUPPORTED 總是非事務(wù)地執(zhí)行,并掛起任何存在的事務(wù)。使用PROPAGATION_NOT_SUPPORTED,也需要使用JtaTransactionManager作為事務(wù)管理器。

6、PROPAGATION_NEVER
總是非事務(wù)地執(zhí)行,如果存在一個(gè)活動(dòng)事務(wù),則拋出異常。
7、PROPAGATION_NESTED

示例:
@Transactional(propagation = Propagation.REQUIRED)
methodA(){
doSomeThingA();
methodB();
doSomeThingB();
}
@Transactional(propagation = Propagation.NEWSTED)
methodB(){
……
}
如果單獨(dú)調(diào)用methodB方法,則按REQUIRED屬性執(zhí)行。如果調(diào)用methodA方法,相當(dāng)于下面的效果:
main(){
Connection con = null;
Savepoint savepoint = null;
try{
con = getConnection();
con.setAutoCommit(false);
doSomeThingA();
savepoint = con2.setSavepoint();
try{
methodB();
} catch(RuntimeException ex) {
con.rollback(savepoint);
} finally {
//釋放資源
}
doSomeThingB();
con.commit();
} catch(RuntimeException ex) {
con.rollback();
} finally {
//釋放資源
}
}
當(dāng)methodB方法調(diào)用之前,調(diào)用setSavepoint方法,保存當(dāng)前的狀態(tài)到savepoint。如果methodB方法調(diào)用失敗,則恢復(fù)到之前保存的狀態(tài)。但是需要注意的是,這時(shí)的事務(wù)并沒(méi)有進(jìn)行提交,如果后續(xù)的代碼(doSomeThingB()方法)調(diào)用失敗,則回滾包括methodB方法的所有操作。嵌套事務(wù)一個(gè)非常重要的概念就是內(nèi)層事務(wù)依賴于外層事務(wù)。外層事務(wù)失敗時(shí),會(huì)回滾內(nèi)層事務(wù)所做的動(dòng)作。而內(nèi)層事務(wù)操作失敗并不會(huì)引起外層事務(wù)的回滾。
特別地:
PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大區(qū)別在于, PROPAGATION_REQUIRES_NEW 完全是一個(gè)新的事務(wù), 而 PROPAGATION_NESTED 則是外部事務(wù)的子事務(wù), 如果外部事務(wù) commit, 嵌套事務(wù)也會(huì)被 commit, 這個(gè)規(guī)則同樣適用于 roll back.
以上都是一個(gè)數(shù)據(jù)源的情況下的事務(wù)處理,那你有沒(méi)有想過(guò)如果多個(gè)數(shù)據(jù)源的情況下,這個(gè)事務(wù)如何得到保證呢?還請(qǐng)留意下次更新【Spring多數(shù)據(jù)源事務(wù)】