Spring事務基礎

前言

我猜大概50%的Java程序員(別問我怎麼知道的,反正我就是,太失敗了!!!)如今僅僅侷限於一個@Transactional註解或者在XML中配置事務相關的東西,而後除了事務級別以外,其餘的事務知識多是空白的。爲了更加全面地學習,因此我就彙總一下Spring事務的知識點,有什麼不對或者補充的,你們記得留言告訴我哈。前端

爲何要事務

關於事務的由來,我就不舉例子了,不少人第一反應就是去銀行存錢(然而我是用花唄的)的操做了。事務的四大特性ACID:原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)、持久性(Durability)java

事務的隔離級別

(1)read uncommited:是最低的事務隔離級別,它容許另一個事務能夠看到這個事務未提交的數據。
(2)read commited:保證一個事物提交後才能被另一個事務讀取。另一個事務不能讀取該事物未提交的數據。
(3)repeatable read:這種事務隔離級別能夠防止髒讀,不可重複讀。可是可能會出現幻象讀。它除了保證一個事務不能被另一個事務讀取未提交的數據以外還避免瞭如下狀況產生(不可重複讀)。
(4)serializable:這是花費最高代價但最可靠的事務隔離級別。事務被處理爲順序執行。除了防止髒讀,不可重複讀以外,還避免了幻象讀程序員

說明:
a.髒讀:指當一個事務正字訪問數據,而且對數據進行了修改,而這種數據尚未提交到數據庫中,這時,另一個事務也訪問這個數據,而後使用了這個數據。由於這個數據尚未提交那麼另一個事務讀取到的這個數據咱們稱之爲髒數據。依據髒數據所作的操做肯能是不正確的。
b.不可重複讀:指在一個事務內,屢次讀同一數據。在這個事務尚未執行結束,另一個事務也訪問該同一數據,那麼在第一個事務中的兩次讀取數據之間,因爲第二個事務的修改第一個事務兩次讀到的數據多是不同的,這樣就發生了在一個事物內兩次連續讀到的數據是不同的,這種狀況被稱爲是不可重複讀。
c.幻象讀:一個事務前後讀取一個範圍的記錄,但兩次讀取的紀錄數不一樣,咱們稱之爲幻象讀(兩次執行同一條 select 語句會出現不一樣的結果,第二次讀會增長一數據行,並無說這兩次執行是在同一個事務中)spring

接口體系

@Transactional註解估計你們都瞭解,那麼咱們先跟蹤一下它的源碼,發現了PlatformTransactionManager這個接口類,具體的接口方法以下:sql

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配置事務apache

一、編程式事務

具體實現以下:編程

import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

//一、注入事務管理器對象
@Autowired
private PlatformTransactionManager txManager;

//二、開啓事務
TransactionStatus status = txManager.getTransaction(new DefaultTransactionDefinition());
//三、提交
txManager.commit(status);
四、回滾
txManager.rollback(status);

使用場景:
在springboot項目開發中,涉及到調用第三方接口,請求第三方接口成功但返回相關交易失敗的話,須要刪除插入表的某條數據,或更新別表中的表狀態同時記錄日誌等,將第三方請求的實際完成狀況返回給前端。安全

二、TransactionProxyFactoryBean實現事務

配置文件:applicationContext.xmlspringboot

<?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>
        <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事務管理器 -->
     <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
    
       <!-- ==================================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">
            <props>
                <!-- 
                    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> -->
            </props>
        </property>
    </bean>
</beans>

這個主要是使用xml配置事務,其實跟如今的@Transactional 的效果同樣。
更多詳細的配置能夠參考文章:https://blog.csdn.net/linhaiyun_ytdx/article/details/78236418

三、基於 @Transactional 的聲明式事務管理

這個最簡單,就暫時不細講。

四、基於Aspectj AOP配置事務

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.TransactionStatus;
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.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 {
        //調用方法以前執行
        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);
}

事務傳播行爲

如下部分摘自「唐大麥」:https://blog.csdn.net/soonfly/article/details/70305683
事務傳播行爲(propagation behavior)指的就是當一個事務方法被另外一個事務方法調用時,這個事務方法應該如何進行。
例如:methodA事務方法調用methodB事務方法時,methodB是繼續在調用者methodA的事務中運行呢,仍是爲本身開啓一個新事務運行,這就是由methodB的事務傳播行爲決定的。

看完仍是以爲有點懵,那就一個個地爲各位簡單介紹一下唄。

一、PROPAGATION_REQUIRED

若是存在一個事務,則支持當前事務。若是沒有事務則開啓一個新的事務。
能夠把事務想像成一個膠囊,在這個場景下方法B用的是方法A產生的膠囊(事務)。

@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
 methodB();
// do something
}

@Transactional(propagation = Propagation.REQUIRED)
public void methodB() {
    // do something
}

單獨調用methodB方法時,由於當前上下文不存在事務,因此會開啓一個新的事務。
調用methodA方法時,由於當前上下文不存在事務,因此會開啓一個新的事務。當執行到methodB時,methodB發現當前上下文有事務,所以就加入到當前事務中來。

二、PROPAGATION_SUPPORTS

若是存在一個事務,支持當前事務。若是沒有事務,則非事務的執行。可是對於事務同步的事務管理器,PROPAGATION_SUPPORTS與不使用事務有少量不一樣。

@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
 methodB();
// do something
}

// 事務屬性爲SUPPORTS
@Transactional(propagation = Propagation.SUPPORTS)
public void methodB() {
    // do something
}

單純的調用methodB時,methodB方法是非事務的執行的。當調用methdA時,methodB則加入了methodA的事務中,事務地執行。

三、PROPAGATION_MANDATORY

若是已經存在一個事務,支持當前事務。若是沒有一個活動的事務,則拋出異常。

@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
 methodB();
// do something
}

// 事務屬性爲MANDATORY
@Transactional(propagation = Propagation.MANDATORY)
public void methodB() {
    // do something
}

當單獨調用methodB時,由於當前沒有一個活動的事務,則會拋出異常throw new IllegalTransactionStateException(「Transaction propagation ‘mandatory’ but no existing transaction found」);當調用methodA時,methodB則加入到methodA的事務中,事務地執行。

四、PROPAGATION_MANDATORY

使用PROPAGATION_REQUIRES_NEW,須要使用 JtaTransactionManager做爲事務管理器。
它會開啓一個新的事務。若是一個事務已經存在,則先將這個存在的事務掛起。

@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
doSomeThingA();
methodB();
doSomeThingB();
// do something else
}


// 事務屬性爲REQUIRES_NEW
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
    // do something
}

當調用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 老是非事務地執行,並掛起任何存在的事務。使用PROPAGATION_NOT_SUPPORTED,也須要使用JtaTransactionManager做爲事務管理器。

六、PROPAGATION_NEVER

老是非事務地執行,若是存在一個活動事務,則拋出異常。

七、PROPAGATION_NESTED

示例:

@Transactional(propagation = Propagation.REQUIRED)
methodA(){
  doSomeThingA();
  methodB();
  doSomeThingB();
}

@Transactional(propagation = Propagation.NEWSTED)
methodB(){
  ……
}

若是單獨調用methodB方法,則按REQUIRED屬性執行。若是調用methodA方法,至關於下面的效果:

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 {
        //釋放資源
    }
}

當methodB方法調用以前,調用setSavepoint方法,保存當前的狀態到savepoint。若是methodB方法調用失敗,則恢復到以前保存的狀態。可是須要注意的是,這時的事務並無進行提交,若是後續的代碼(doSomeThingB()方法)調用失敗,則回滾包括methodB方法的全部操做。嵌套事務一個很是重要的概念就是內層事務依賴於外層事務。外層事務失敗時,會回滾內層事務所作的動做。而內層事務操做失敗並不會引發外層事務的回滾。

特別地:
PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大區別在於, PROPAGATION_REQUIRES_NEW 徹底是一個新的事務, 而 PROPAGATION_NESTED 則是外部事務的子事務, 若是外部事務 commit, 嵌套事務也會被 commit, 這個規則一樣適用於 roll back.

以上都是一個數據源的狀況下的事務處理,那你有沒有想過若是多個數據源的狀況下,這個事務如何獲得保證呢?還請留意下次更新【Spring多數據源事務】

參考文章

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

最後

更多文章可關注公衆號【愛編碼】,回覆2019有相關資料哦。

相關文章
相關標籤/搜索