背景
手写 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 对象实例