Spring事務(wù)基礎(chǔ)

前言

我猜大概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ù)】

參考文章

https://blog.csdn.net/soonfly/article/details/70305683

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容