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

1、編程式事務

具體實現如下:

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項目開發中,涉及到調用第三方接口,請求第三方接口成功但返回相關交易失敗的話,需要刪除插入表的某條數據,或更新別表中的表狀態同時記錄日誌等,將第三方請求的實際完成情況返回給前端。

2、TransactionProxyFactoryBean實現事務

配置文件: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>

        <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

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

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

4、基於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);

}

事務傳播行爲

 

事務傳播行爲(propagation behavior)指的就是當一個事務方法被另一個事務方法調用時,這個事務方法應該如何進行。

例如:methodA事務方法調用methodB事務方法時,methodB是繼續在調用者methodA的事務中運行呢,還是爲自己開啓一個新事務運行,這就是由methodB的事務傳播行爲決定的。

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

1、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發現當前上下文有事務,因此就加入到當前事務中來。

2、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的事務中,事務地執行。

3、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的事務中,事務地執行。

4、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之外的其它代碼導致的結果卻被回滾了

5、PROPAGATION_NOT_SUPPORTED

PROPAGATION_NOT_SUPPORTED 總是非事務地執行,並掛起任何存在的事務。使用PROPAGATION_NOT_SUPPORTED,也需要使用JtaTransactionManager作爲事務管理器。

6、PROPAGATION_NEVER

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

7、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.

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章