首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring事务说明与自实现 顶

Spring事务说明与自实现 顶

作者头像
算法之名
发布2019-08-20 10:17:08
3250
发布2019-08-20 10:17:08
举报
文章被收录于专栏:算法之名算法之名

要使用Springboot的事务其实非常简单,在启动类上添加@EnableTransactionManagement,在Service的类或者方法上使用@Transactional就可以了。

事务本身的4大特性

  • 原子性(Atomicity) 指事务必须是一个不可分割的整体
  • 一致性(Consistency) 指执行完数据库操作后,数据不会被破坏
  • 隔离性(Isolation) 保证数据库操作之间,彼此没有任何干扰
  • 持久性(Durability) 保证永久的存放在磁盘中

其中隔离性又分四个级别,它们依次向下,级别越来越高,并发性越来越差,安全性越来越高

  • 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原理与自实现

首先在pom中增加JDBC的引用,根据你数据库版本的不同而不同,我这里是针对mysql 8的。

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

定义事务注解

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 {
}

实现一个线程隔离类

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
数据库
云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档