要使用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