Spring事務說明與自實現

要使用Springboot的事務其實很是簡單,在啓動類上添加@EnableTransactionManagement,在Service的類或者方法上使用@Transactional就能夠了。java

事務自己的4大特性mysql

  • 原子性(Atomicity)      指事務必須是一個不可分割的總體
  • 一致性(Consistency)  指執行完數據庫操做後,數據不會被破壞
  • 隔離性(Isolation)       保證數據庫操做之間,彼此沒有任何干擾
  • 持久性(Durability)     保證永久的存放在磁盤中

其中隔離性又分四個級別,它們依次向下,級別愈來愈高,併發性愈來愈差,安全性愈來愈高sql

  • READ_UNCOMMITTED   容許存在髒讀(事務A讀取了事務B未提交的數據,並在這個基礎上又作了其餘操做)
  • READ_COMMITTED        不容許髒讀,容許不可重複讀(事務A讀取了事務B已提交的更改數據)
  • REPEATABLE_READ         不容許髒讀,不可重複讀,容許幻讀(事務A讀取了事務B已提交的新增數據)
  • SERIALIZABLE                 所有不容許,作到徹底隔離

而Spring是以7種事務傳播行爲來區別的,假設事務從方法A傳播到方法B,用戶須要面對方法B,須要知道方法A有事務嗎?數據庫

  • PROPAGATION_REQUIRED    若是沒有,就新建一個事務;若是有,就加入當前事務。是Spring默認的事務傳播行爲,適合絕大多數狀況。
  • PROPAGATION_REQUIRES_NEW  若是沒有,就新建一個事務;若是有,就將當前事務掛起,意思就是建立了一個新事務,它和原來的事務沒有任何關係。
  • PROPAGATION_NESTED      若是沒有,就新建一個事務;若是有,就在當前事務中嵌套其餘事務,也就是「嵌套事務」,所嵌套的子事務與主事務之間是有關聯關係的(當主事務提交或回滾,子事務也會提交或回滾)。
  • PROPAGATION_SUPPORTS 若是沒有,就以非事務方式執行;若是有,就使用當前事務。這種方式很是隨意,沒有就沒有,有就有,有點無所謂的態度,反正是支持的。
  • PROPAGATION_NOT_SUPPORTED 若是沒有,就以非事務方式執行;若是有,就將當前事務掛起,這種方式很是強硬,沒有就沒有,有也不支持,掛起,無論。
  • PROPAGATION_NEVER 若是沒有,就以非事務方式執行;若是有,就拋出異常。這種方式更強硬,沒有就沒有,有了反而報錯,從不支持事務。
  • PROPAGATION_MANDATORY 若是沒有,就拋出異常;若是有,就使用當前事務。這種方式能夠說是最強硬的,沒有事務就直接報錯,必需要有事務。

具體配置爲安全

@Transactional(isolation = Isolation.DEFAULT,propagation = Propagation.REQUIRED)

其中isolation和propagation取值都是枚舉。併發

如今咱們來本身實現一個事務管理特性,代碼承接於 AOP原理與自實現ide

首先在pom中增長JDBC的引用,根據你數據庫版本的不一樣而不一樣,我這裏是針對mysql 8的。測試

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.11</version>
</dependency>

定義事務註解url

package com.guanjian.annotion;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 定義須要事務控制的方法
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Transacion {
}

實現一個線程隔離類spa

package com.guanjian.proxy;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 線程隔離
 */
public class ThreadLocal<T> {
    private Map<Thread,T> container = new ConcurrentHashMap<>();

    public void set(T value) {
        container.put(Thread.currentThread(),value);
    }
    public T get() {
        Thread thread = Thread.currentThread();
        T value = container.get(thread);
        if (value == null && !container.containsKey(thread)) {
            value = initialValue();
            container.put(thread,value);
        }
        return value;
    }

    public void remove() {
        container.remove(Thread.currentThread());
    }
    protected T initialValue() {
        return null;
    }
}

數據庫操做助手類

package com.guanjian.util;

import com.guanjian.proxy.ThreadLocal;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

/**
 * 數據庫操做助手類
 */
public class DatabaseHelper {
    private static final String driver = "com.mysql.cj.jdbc.Driver";
    private static final String url = "jdbc:mysql://192.168.1.102:3306/cloud_user?useSSL=FALSE&serverTimezone=GMT%2B8";
    private static final String username = "root";
    private static final String password = "root";
    //用於放置數據庫鏈接的局部線程變量(使每一個線程都擁有本身的鏈接),用於線程隔離,生成環境請使用數據庫鏈接池
    private static ThreadLocal<Connection> CONNECTION_HOLDER = new ThreadLocal<>();

    /**
     * 開啓事務
     */
    public static void beginTransaction() {
        Connection conn = getConnection();
        if (conn != null) {
            try {
                conn.setAutoCommit(false);
            } catch (SQLException e) {
                System.out.println("Begin transaction failure " + e);
                throw new RuntimeException(e);
            }finally {
                CONNECTION_HOLDER.set(conn);
            }
        }
    }

    /**
     * 提交事務
     */
    public static void commitTransaction() {
        Connection conn = getConnection();
        if (conn != null) {
            try {
                conn.commit();
                conn.close();
            } catch (SQLException e) {
                System.out.println("commit transaction failure " + e);
                throw new RuntimeException(e)
            } finally {
                CONNECTION_HOLDER.remove();
            }
        }
    }

    /**
     * 回滾事務
     */
    public static void rollbackTransaction() {
        Connection conn = getConnection();
        if (conn != null) {
            try {
                conn.rollback();
                conn.close();
            } catch (SQLException e) {
                System.out.println("rollback transaction failure" + e);
                throw new RuntimeException(e);
            } finally {
                CONNECTION_HOLDER.remove();
            }
        }
    }

    private static Connection getConnection(){
        Connection conn = null;
        try {
            Class.forName(driver);
            conn = DriverManager.getConnection(url,username,password);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return conn;
    }

}

使用事務代理類對標記有@Transaction的方法攔截,進行代理加強

package com.guanjian.proxy;

import com.guanjian.annotion.Transacion;
import com.guanjian.util.DatabaseHelper;

import java.lang.reflect.Method;

/**
 * 事務代理
 */
public class TransactionProxy implements Proxy {
    //線程事務控制標誌,保證同一個線程中事務控制邏輯只會執行一次
    private static final ThreadLocal<Boolean> FLAG_HOLDER = new ThreadLocal<Boolean>() {
        @Override
        protected Boolean initialValue() {
            return false;
        }
    };
    @Override
    public Object doProxy(ProxyChain proxyChain) throws Throwable {
        Object result;
        boolean flag = FLAG_HOLDER.get(); //默認false
        Method method = proxyChain.getTargetMethod();
        //檢查方法是否帶有@Transaction註解且是同一個線程執行的,進行代理加強
        if (!flag && method.isAnnotationPresent(Transacion.class)) {
            FLAG_HOLDER.set(true);
            try {
                DatabaseHelper.beginTransaction();
                System.out.println("begin transaction");
                //跟代理鏈雙向遞歸
                result = proxyChain.doProxyChain();
                DatabaseHelper.commitTransaction();
                System.out.println("commit transaction");
            }catch (Exception e) {
                DatabaseHelper.rollbackTransaction();
                System.out.println("rollback transaction");
                throw e;
            }finally {
                //移除該線程
                FLAG_HOLDER.remove();
            }
        }else { //若是沒有註解,則只執行被代理類實例本方法
            result = proxyChain.doProxyChain();
        }
        return result;
    }
}

在AOPHelper中進行修改

/**
 * 建立全部的AOP類,事務類與與之對應的目標類集合的映射
 * @return
 * @throws Exception
 */
private static Map<Class<?>,Set<Class<?>>> createProxyMap() throws Exception {
    Map<Class<?>,Set<Class<?>>> proxyMap = new HashMap<>();
    addAspectProxy(proxyMap);
    addTransactionProxy(proxyMap);
    return proxyMap;
}

/**
 * 增長切面代理
 * @param proxyMap
 * @throws Exception
 */
private static void addAspectProxy(Map<Class<?>,Set<Class<?>>> proxyMap) throws Exception {
    //獲取切面代理類(抽象類)的全部實現類(子類)
    Set<Class<?>> proxyClassSet = ClassHelper.getClassSetBySuper(AspectProxy.class);
    for (Class<?> proxyClass:proxyClassSet) {
        //實現類是否有@Aspect標籤
        if (proxyClass.isAnnotationPresent(Aspect.class)) {
            //獲取該標籤
            Aspect aspect = proxyClass.getAnnotation(Aspect.class);
            //獲取全部目標類集合
            Set<Class<?>> targetClassSet = createTargetClassSet(aspect);
            //將代理類實例與該集合添加map映射
            proxyMap.put(proxyClass,targetClassSet);
        }
    }
}
/**
 * 增長事務代理
 * @param proxyMap
 */
 private static void addTransactionProxy(Map<Class<?>,Set<Class<?>>> proxyMap) {
     //以@Componet標籤爲開啓事務代理的類標籤
    Set<Class<?>> componentClassSet = ClassHelper.getClassSetByAnnotation(Component.class);
    proxyMap.put(TransactionProxy.class,componentClassSet);
 }

測試

@Component
public class Test4 {
    @Transacion
    public void show() {
        System.out.println("aaa");
    }
}
public class Test {
    public static void main(String[] args) {
        //掃描包
        Manager.scanAndImp("com.guanjian.test");
        //初始化AopHelper
        ClassUtil.loadClass(AopHelper.class.getName(),true);
        //這裏其實拿到的是代理類的實例,代理類是目標類的子類
        Test4 test4 = (Test4)Manager.getBean(Test4.class);
        test4.show();
    }
}

運行結果

aop Class loaded

begin transaction aaa commit transaction

相關文章
相關標籤/搜索