写点什么

女朋友不懂 Spring 事务原理,今天给她讲清楚了!

  • 2023-03-28
    湖南
  • 本文字数:3911 字

    阅读完需:约 13 分钟

女朋友最近在找新工作,被面试官频繁问 Spring 事务原理,女朋友没有看过 Spring 源码,一直要我给她讲原理,看到女朋友十分低落的表情,我下定决心一定要给他讲清楚了。

传统事务的做法

我一开始问她知不知道假如没有 Spring 要怎么实现事务,她说知道,我就要他把传统事务的流程图画出来给我看下,下面就是她给出的流程图:

传统事务存在哪些问题

果然计算机科班出身的女朋友,技术底子还是阔以的[你强],我再问她这种传统处理事务的方案有哪些局限或者短板呢? 她说,除了 CRUD 的业务代码,还需要写事务管理相关的代码,平时的工作量就加大了,这个确实如此,传统的事务做法对程序员的工作量大大提高,同时也补充了我的看法:

  1. 传统事务代码与业务代码耦合,强度入侵业务代码,可扩展性差

  2. 如果一个业务功能里同时存在多个事务切换的时候,代码编写的就会非常复杂


正是为了解决这些缺陷,Spring 团队就打造了 Spring 事务组件,这个模块组件封装了传统事务功能的逻辑,业务开发者不用关心事务管理代码,只管专注于业务开发,多么美哉!

Spring 事务组件做了哪些伟大的事?

1、Spring 定义了事务操作规范,提供了顶层统一的编程模型抽象,不管是 Java 事务 API,还是 Hibernate,下面就是 Spring 定义的顶层接口,规范了对事务操作定义:

public interface PlatformTransactionManager {    //获取事务    TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;    //提交事务    void commit(TransactionStatus var1) throws TransactionException;    //回滚事务    void rollback(TransactionStatus var1) throws TransactionException;}
复制代码

2、同时支持声明式事务和编程式事务 女朋友又开始问了,什么是编程式事务?什么是声明式事务呢? 编程式事务和传统事务类似,它是和业务代码耦合在一起的,只不过 Spring 团队对事务管理进行了封装,提供了工具类

org.springframework.transaction.support.TransactionTemplate
复制代码

使用姿势如下面所示:

transactionTemplate.execute(() -> {    //业务代码    userService.addUser(user);    scoreService.addScore(score);});
复制代码

那什么是声明式事务呢? 声明式事务就是通过注解的形式完成事务功能,如下面的代码可以等同于上面编程式事务,使我们的业务代码在事务中执行。

@Transactionalpublic void doTransaction() {    userService.addUser(user);    scoreService.addScore(score);}
复制代码

Spring 事务实现原理剖析

女朋友开始感兴趣了,这些 Spring 是如何做到的呢?

首先说编程式事务,这个比较简单,扒开他的源码可知,他就是通过将业务代码以函数表达式传入一个工具方法,方法里在执行业务代码前后织入事务管理的逻辑,如下面代码:

public class TransactionTemplate {
public <T> T execute(TransactionCallback<T> action) throws TransactionException { //获取数据库连接 TransactionStatus status = transactionManager.getTransaction(this); Object result; try { //执行crud result = action.doInTransaction(status); } catch (Error | RuntimeException var5) { //异常回滚 this.rollbackOnException(status, var5); throw var5; } catch (Throwable var6) { //异常回滚 this.rollbackOnException(status, var6); throw new UndeclaredThrowableException(var6, "TransactionCallback threw undeclared checked exception"); } //执行crud成功,提交事务 this.transactionManager.commit(status); return result; } }}
复制代码

看到这个实现,就非常清楚了,Spring 将事务管理代码和函数式编程结合完成了编程式事务的组装。

下面我重点讲下声明式事务的实现逻辑:


虽然表面上看着 Spring 通过一个注解就完成了事务的功能,实际上底层有很多的逻辑,它需要能够拦截事务方法,并且在事务方法前后嵌入事务管理逻辑,完成事务开启,提交,回滚等逻辑,在 Spring 生态中,AOP 技术就是一种在方法前后嵌入一些通用代码的手段,确实如此,Spring 声明式事务就是借助 Spring 的 AOP 能力实现的。


TransactionInterceptor 就是 Spring 内部定义的一个切面,她实现了 MethodInterceptor 接口就像我们平时定义一个切面统一打印日志一样,这个切面是 Spring 内置的一个切面,专门用来处理数据库事务的。

在 ProxyTransactionManagementConfiguration 配置类中,对事务切面进行了定义:

//定义拦截器@Bean@Role(BeanDefinition.ROLE_INFRASTRUCTURE)public TransactionInterceptor transactionInterceptor() {   TransactionInterceptor interceptor = new TransactionInterceptor();   interceptor.setTransactionAttributeSource(transactionAttributeSource());   if (this.txManager != null) {      interceptor.setTransactionManager(this.txManager);   }   return interceptor;}@Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor() {   BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();   advisor.setTransactionAttributeSource(transactionAttributeSource());   advisor.setAdvice(transactionInterceptor());   if (this.enableTx != null) {      advisor.setOrder(this.enableTx.<Integer>getNumber("order"));   }   return advisor;}
复制代码

这样 Spring 容器启动之后,就有一个全局的事务切面了。拦截到 @Transaction 注解的方法时,会触发下面方法执行:

public class TransactionInterceptor{    @Override	public Object invoke(MethodInvocation invocation) throws Throwable {				Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);                //在事务中执行目标方法		return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);	}}
复制代码

invokeWithinTransaction 方法就是 Spring 事务嵌入事务管理方法到业务方法前后的具体实现了。


看里面的源码就非常熟悉了,这个和编程式事务工具类 TransactionTemplate 的实现如出一辙,女朋友终于恍然大悟,原来是这样。

public class TransactionInterceptor{    	protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,			final InvocationCallback invocation) throws Throwable {
TransactionAttributeSource tas = getTransactionAttributeSource(); final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null); final PlatformTransactionManager tm = determineTransactionManager(txAttr); final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
//获取事务 TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification); Object retVal = null; try { //执行目标方法crud retVal = invocation.proceedWithInvocation(); } catch (Throwable ex) { // crud执行异常回滚事务 completeTransactionAfterThrowing(txInfo, ex); throw ex; } finally { cleanupTransactionInfo(txInfo); } //提交事务 commitTransactionAfterReturning(txInfo); return retVal; }}
复制代码

看到这里,其实 Spring 声明式事务的实现思路非常清晰了,无非就是借助 Spring 的 AOP 能力,内置了一个切面,并且在事务方法前后织入了事务管理的操作。 女朋友非常高兴,今天我的晚餐又可以加鸡腿了

彩蛋

能够看到这里的朋友真的是非常感激,我相信技术的力量是无穷的。 最后给大家一个安利,Spring 事务虽然封装好了,一个注解就可以完成事务的功能,但是有没有一种可能?你的功能需要感知你的业务代码的事务是成功还是失败了?


Spring 团队想到了这种可能,提供了一个口子给我们,这个就是事务同步器!!!,事务同步器是异步的,他会在事务提交前后通知到你,只要继承下面的抽象类即可。

public abstract class TransactionSynchronizationAdapter implements TransactionSynchronization, Ordered {
@Override public int getOrder() { return Ordered.LOWEST_PRECEDENCE; }
@Override public void suspend() { }
@Override public void resume() { }
@Override public void flush() { }
@Override public void beforeCommit(boolean readOnly) { }
@Override public void beforeCompletion() { }
@Override public void afterCommit() { //这里可以感知事务提交完成 }
@Override public void afterCompletion(int status) { //这里感知事务完成 }
}
复制代码

哈哈哈,这个实现有没有很优雅,每次看 Spring 的组件都会在心里给 Spring 团队的大神点赞,封装好组件给我们用,同时提供扩展点给我们,真的非常给力。


看到这个地方的朋友,有没有觉得可以 get 到一点新姿势新技能,有的话麻烦一键三连,哈哈哈哈。


作者:服务端技术栈

链接:https://juejin.cn/post/7212142580708802615

来源:稀土掘金

用户头像

还未添加个人签名 2021-07-28 加入

公众号:该用户快成仙了

评论

发布
暂无评论
女朋友不懂Spring事务原理,今天给她讲清楚了!_Java_做梦都在改BUG_InfoQ写作社区