手写 mini Spring Tx

背景

手写spring进行到了事务了,上2篇文章介绍了手写spring IOC,spring Aop,这篇文章在介绍手写spring tx

概述

事务管理对于企业应用来说是至关重要的,即使出现异常情况,它也可以保证数据的一致性。
Spring Framework对事务管理提供了一致的抽象,其特点如下:

  • 为不同的事务API提供一致的编程模型,比如JTA(Java Transaction API), JDBC, Hibernate, JPA(Java Persistence API和JDO(Java Data Objects)
  • 支持声明式事务管理,特别是基于注解的声明式事务管理,简单易用
  • 提供比其他事务API如JTA更简单的编程式事务管理API
  • 与spring数据访问抽象的完美集成

流程实现

从图中我们能够知道spring Tx也是通过AOP增强,反射代理生成一个代理对象,进行对业务方法增强,处理我们的事务

有时候我们会在一个方法里面执行多个sql语句,要想保证事务,必须保证这些是在同一个connection连接,同一个事务,这样回滚才有效

代码实现

基于上次在IOC和AOP的基础上进行改动,增加相关TX逻辑代码

创建一个生成CGLIB的方法类

package org.springframework.aop;

import lombok.AllArgsConstructor;
import lombok.Data;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import org.springframework.aop.tx.TransactionManger;

import java.sql.Connection;
import java.util.List;
import java.util.Map;

@Data
@AllArgsConstructor
public class CglibProxy {

    private Class<?> targetClass;

    public Object getInstance(List<String> targetMethods, Map<String, Class<? extends Throwable>[]> map) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(targetClass);
        enhancer.setCallback((MethodInterceptor) (o, method, args, methodProxy) -> {
            Object result = null;
            if (targetMethods.contains(method.getName())) {
                Connection connection = new TransactionManger().getConnection();
                connection.setAutoCommit(false);
                try {
                    System.out.println("开启事务");
                    result = methodProxy.invokeSuper(o, args);
                    connection.commit();
                    System.out.println("提交事务");
                } catch (Throwable e) {
                    //如果用户指定了异常,只根据这些异常子类回滚
                    Class<? extends Throwable>[] exArr = map.get(method.getName());
                    if (exArr != null) {
                        for (Class<? extends Throwable> ex : exArr) {
                            if (ex.isAssignableFrom(e.getClass())) {
                                System.out.println("回滚事务");
                                connection.rollback();
                            } else {
                                connection.commit();
                            }
                        }
                    }
                    e.printStackTrace();
                }
            } else {
                result = methodProxy.invokeSuper(o, args);
            }
            return result;
        });
        return enhancer.create();
    }
}

这个方法主要是用来为添加事务注解的方法或类生成一个cglib代理对象,进行方法增强,在catch里面进行事务回滚,能够支持用户填写rollbackFor异常,只有用户填写的异常或异常子类才会回滚,默认为 ·RuntimeException·异常和其子类

创建连接数据库工具类

package com.xiaohu.springioc.mysql;

import lombok.SneakyThrows;

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

public class MySQLConnectionExample {

    static final String JDBC_DRIVER = "com.mysql.cj.jdbc.Driver";
    static final String DB_URL = "jdbc:mysql://localhost:3306/xxxx?rewriteBatchedStatements=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=Asia/Shanghai";

    // 数据库的用户名与密码,需要根据你的设置
    static final String USER = "xxx";
    static final String PASS = "xxxx";

    @SneakyThrows
    public static Connection connectToDatabase() {
        try {
            Class.forName(JDBC_DRIVER);
            return DriverManager.getConnection(DB_URL, USER, PASS);
        } catch (Exception e) {
            throw new SQLException("无法获取connection");
        }
    }

    @SneakyThrows
    public static void close(Connection connection) {
        if (connection != null) {
            connection.close();
        }
    }

}

创建事务管理器

package org.springframework.aop.tx;

import com.xiaohu.springioc.mysql.MySQLConnectionExample;
import org.springframework.stereotype.Component;

import java.sql.Connection;

/**
 * 事务管理器
 */
@Component
public class TransactionManger {
    public static ThreadLocal<Connection> connectionThreadLocal;

    static {
        connectionThreadLocal = new ThreadLocal<>();
        connectionThreadLocal.set(MySQLConnectionExample.connectToDatabase());
    }

    public Connection getConnection() {
        return connectionThreadLocal.get();
    }
}

用来获取connection,保证处于同一个connection事务中

创建事务注解 @Transactional

package org.springframework.aop.annotation;

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

@Retention(RetentionPolicy.RUNTIME)
//指定该注解可以作用在类上
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Transactional {

    Class<? extends Throwable>[] rollBackFor() default RuntimeException.class;
}

IOC容器中构建事务对象

    private void buildTransactionalList(Set<String> classFiles) {
        for (String classFile : classFiles) {
            classFile = classFile.replace(File.separator, ".").replace(".class", "");
            Class<?> c = Class.forName(classFile);
            Method[] methods = c.getDeclaredMethods();
            //事务注解加载类,其下所有方法都需要添加事务
            if (c.isAnnotationPresent(Transactional.class) && methods.length > 0) {
                Transactional annotation = c.getAnnotation(Transactional.class);
                Class<? extends Throwable>[] exceptionArray = annotation.rollBackFor();
                List<String> methodList = Arrays.stream(methods).map(Method::getName).collect(Collectors.toList());
                if (exceptionArray.length > 0) {
                    for (String method : methodList) {
                        txExceptionMap.put(method, exceptionArray);
                    }
                }
                transactionalMap.computeIfAbsent(c, k -> new HashSet<>()).addAll(methodList);
            }
            //如果没在类上加注解,只方法上加了事务注解
            for (Method method : methods) {
                if (method.isAnnotationPresent(Transactional.class)) {
                    Transactional annotation = method.getAnnotation(Transactional.class);
                    Class<? extends Throwable>[] exceptionArray = annotation.rollBackFor();
                    transactionalMap.computeIfAbsent(c, k -> new HashSet<>()).add(method.getName());
                    if (exceptionArray.length > 0) {
                        txExceptionMap.put(method.getName(), exceptionArray);
                    }
                }
            }
        }
    }

用来获取包扫描路径下,哪些类,哪些方法标记了事务注解,如果是在类标记了注解,则该类所有方法都默认生效跟类一样的事务效果,如果方法上特别标注了,默认生效方法上的txExceptionMap,后面的会覆盖前面的

处理事务对象

  private void doTransaction() {
        List<String> allMethods = new ArrayList<>();
        for (Set<String> value : transactionalMap.values()) {
            allMethods.addAll(value);
        }
        for (Map.Entry<Class<?>, Set<String>> entry : transactionalMap.entrySet()) {
            Class<?> sourceClass = entry.getKey();
            CglibProxy cglibProxy = new CglibProxy(sourceClass);
            Object proxyInstance = cglibProxy.getInstance(allMethods, txExceptionMap);
            changeIocBean(sourceClass, proxyInstance);
        }
    }

为构建好的事务对象,生成cglib代理对象,并替换原本ioc容器中的bean对象实例

refresh方法

源代码地址:https://gitee.com/xiaohu88/spring-ioc.git

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值