我的编程空间,编程开发者的网络收藏夹
学习永远不晚

使用SpringBoot项目实现一个本地事务管理功能

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

使用SpringBoot项目实现一个本地事务管理功能

使用SpringBoot项目实现一个本地事务管理功能?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。

SpringBoot 事务

一直在用 SpringBoot 中的 @Transactional 来做事务管理,但是很少没想过 SpringBoot 是如何实现事务管理的,今天从源码入手,看看 @Transactional 是如何实现事务的,最后我们结合源码的理解,自己动手写一个类似的注解来实现事务管理,帮助我们加深理解。

1.1. 事务的隔离级别

事务为什么需要隔离级别呢?这是因为在并发事务情况下,如果没有隔离级别会导致如下问题:

  • 脏读 (Dirty Read) :当A事务对数据进行修改,但是这种修改还没有提交到数据库中,B事务同时在访问这个数据,由于没有隔离,B获取的数据有可能被A事务回滚,这就导致了数据不一致的问题。

  • 丢失修改 (Lost To Modify):当A事务访问数据100,并且修改为100-1=99,同时B事务读取数据也是100,修改数据100-1=99,最终两个事务的修改结果为99,但是实际是98。事务A修改的数据被丢失了。

  • 不可重复读 (Unrepeatable Read):指A事务在读取数据X=100的时候,B事务把数据X=100修改为X=200,这个时候A事务第二次读取数据X的时候,发现X=200了,导致了在整个A事务期间,两次读取数据X不一致了,这就是不可重复读。

  • 幻读 (Phantom Read):幻读和不可重复读类似。幻读表现在,当A事务读取表数据时候,只有3条数据,这个时候B事务插入了2条数据,当A事务再次读取的时候,发现有5条记录了,平白无故多了2条记录,就像幻觉一样。

不可重复读 VS 幻读

不可重复读的重点是修改 :同样的条件 , 你读取过的数据 , 再次读取出来发现值不一样了,重点在更新操作。
幻读的重点在于新增或者删除:同样的条件 , 第 1 次和第 2 次读出来的记录数不一样,重点在增删操作。

所以,为了避免上述的问题,事务中就有了隔离级别的概念,在Spring中定义了五种表示隔离级别的常量 TransactionDefinition:

  • ISOLATION_DEFAULT:数据库默认的隔离级别,MySQL默认采用的 REPEATABLE_READ 隔离级别。

  • ISOLATION_READ_UNCOMMITTED:最低的隔离级别,允许读取未提交的数据变更,可能会导致脏读、幻读或不可重复读。

  • ISOLATION_READ_COMMITTED:允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。

  • ISOLATION_REPEATABLE_READ:对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。MySQL中通过MVCC解决了该隔离级别下出现幻读的可能。

  • ISOLATION_SERIALIZABLE:串行化隔离级别,该级别可以防止脏读、不可重复读以及幻读,但是串行化会影响性能。

1.2. Spring中事务的传播机制

为什么Spring中要搞一套事务的传播机制呢?这是Spring给我们提供的事务增强工具,主要是解决方法之间调用,事务如何处理的问题。比如有方法A、方法B和方法C,在A中调用了方法B和方法C。伪代码如下:

MethodA() { MethodB(); MethodC();}

假设三个方法中都开启了自己的事务,那么他们之间是什么关系呢?MethodA的回滚会影响MethodB和MethodC吗?Spring中的事务传播机制就是解决这个问题的。
Spring中定义了七种事务传播行为:

  • PROPAGATION_REQUIRED: 如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。

  • PROPAGATION_SUPPORTS: 如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。但是对于事务同步的事务管理器,PROPAGATION_SUPPORTS与不使用事务有少许不同。

  • PROPAGATION_MANDATORY: 如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。

  • PROPAGATION_REQUIRES_NEW: 总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起。

  • PROPAGATION_NOT_SUPPORTED: 总是非事务地执行,并挂起任何存在的事务。

  • PROPAGATION_NEVER: 总是非事务地执行,如果存在一个活动事务,则抛出异常。

  • PROPAGATION_NESTED: 如果一个活动的事务存在,则运行在一个嵌套的事务中。 如果没有活动事务, 则按 TransactionDefinition.PROPAGATION_REQUIRED 属性执行。

1.3. Spring中事务如何实现异常回滚的

回顾完了事务的相关知识,接下来我们正式来研究下 Spring Boot 中如何通过 @Transactional 来管理事务的,我们重点看看它是如何实现回滚的。
在 Spring 中 TransactionInterceptor 和 PlatformTransactionManager 这两个类是整个事务模块的核心,我们重点研究下这两个类的源码。

  • TransactionInterceptor 负责拦截方法执行,进行判断是否需要提交或者回滚事务。

  • PlatformTransactionManager 是 Spring 中的事务管理接口,真正定义了事务如何回滚和提交。

TransactionInterceptor 类中的代码有很多,我简化一下逻辑,方便说明:

 // 以下代码省略部分内容 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 简化逻辑如下:

 // 以下代码省略部分内容 protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass, final InvocationCallback invocation) throws Throwable {  Object retVal;  try {   // 调用真正的方法体   retVal = invocation.proceedWithInvocation();  }  catch (Throwable ex) {   // 如果出现异常,执行事务异常处理   completeTransactionAfterThrowing(txInfo, ex);   throw ex;  }  finally {   // 最后做一下清理工作,主要是缓存和状态等   cleanupTransactionInfo(txInfo);  }  // 如果没有异常,直接提交事务  commitTransactionAfterReturning(txInfo);  return retVal; }

事务出现异常回滚的逻辑 completeTransactionAfterThrowing 如下:

 // 以下代码省略部分内容 protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {  // 判断是否需要回滚,判断的逻辑就是看有没有声明事务属性,同时判断是不是在目前的这个异常中执行回滚  if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {   // 执行回滚   txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());  }  else {   // 否则不需要回滚,直接提交即可   txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());  } }

上面的代码已经把 Spring 的事务的基本原理说清楚了,如何进行判断执行事务,如何回滚。下面到了真正执行回滚逻辑的代码中 PlatformTransactionManager 接口的子类,我们以 JDBC 的事务为例,DataSourceTransactionManager 就是 jdbc 的事务管理类。跟踪上面的代码rollback(txInfo.getTransactionStatus()) 可以发现最终执行的代码如下:

 @Override protected void doRollback(DefaultTransactionStatus status) {  DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();  Connection con = txObject.getConnectionHolder().getConnection();  if (status.isDebug()) {   logger.debug("Rolling back JDBC transaction on Connection [" + con + "]");  }  try {   // 调用jdbc的 rollback进行回滚事务   con.rollback();  }  catch (SQLException ex) {   throw new TransactionSystemException("Could not roll back JDBC transaction", ex);  } }

这里小结下 Spring 中事务的实现思路,Spring 主要依靠 TransactionInterceptor 来拦截执行方法体,判断是否开启事务,然后执行事务方法体,方法体中 catch 住异常,接着判断是否需要回滚,如果需要回滚就委托真正的 TransactionManager 比如 JDBC 中的 DataSourceTransactionManager 来执行回滚逻辑。提交事务也是同样的道理。
这里用个流程图展示下思路:

使用SpringBoot项目实现一个本地事务管理功能

2. 手写注解实现事务回滚

我们弄清楚了 Spring 的事务执行流程,那我们可以模仿着自己写一个注解,实现遇到指定异常就回滚的功能。这里持久层就以最简单的 JDBC 为例。我们先梳理下需求,首先注解我们可以基于 Spring 的 AOP 来实现,接着既然是 JDBC,那么我们需要一个类来帮我们管理连接,用来判断异常是否回滚或者提交。

2.1. Maven 加入依赖

  <dependency>   <groupId>org.springframework.boot</groupId>   <artifactId>spring-boot-starter-aop</artifactId>  </dependency>  <dependency>   <groupId>org.springframework.boot</groupId>   <artifactId>spring-boot-starter-jdbc</artifactId>  </dependency>

2.2. 新建一个注解

@Target({ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Inherited@Documentedpublic @interface MyTransaction { // 指定异常回滚 Class<? extends Throwable>[] rollbackFor() default {};}

2.3. 新建连接管理器

该类帮助我们管理连接,该类的核心功能是把取出的连接对象绑定到线程上,方便在 AOP 处理中取出,进行提交或者回滚操作。

@Componentpublic class DataSourceConnectHolder { @Autowired private DataSource dataSource;  ThreadLocal<Connection> resources = new NamedThreadLocal<>("Transactional resources"); public Connection getConnection() {  Connection con = resources.get();  if (con != null) {   return con;  }  try {   con = dataSource.getConnection();   // 为了体现事务,全部设置为手动提交事务   con.setAutoCommit(false);  } catch (SQLException e) {   e.printStackTrace();  }  resources.set(con);  return con; } public void cleanHolder() {  Connection con = resources.get();  if (con != null) {   try {    con.close();   } catch (SQLException e) {    e.printStackTrace();   }  }  resources.remove(); }}

2.4. 新建一个切面

这部分是事务处理的核心,先获取注解上的异常类,然后捕获住执行的异常,判断异常是不是注解上的异常或者其子类,如果是就回滚,否则就提交。

@Aspect@Componentpublic class MyTransactionAopHandler {  @Autowired private DataSourceConnectHolder connectHolder;  Class<? extends Throwable>[] es; // 拦截所有MyTransaction注解的方法 @org.aspectj.lang.annotation.Pointcut("@annotation(你的包路径.MyTransaction)") public void Transaction() { } @Around("Transaction()") public Object TransactionProceed(ProceedingJoinPoint proceed) throws Throwable {  Object result = null;  Signature signature = proceed.getSignature();  MethodSignature methodSignature = (MethodSignature) signature;  Method method = methodSignature.getMethod();  if (method == null) {   return result;  }  MyTransaction transaction = method.getAnnotation(MyTransaction.class);  if (transaction != null) {   es = transaction.rollbackFor();  }  try {   result = proceed.proceed();  } catch (Throwable throwable) {   // 异常处理   completeTransactionAfterThrowing(throwable);   throw throwable;  }  // 直接提交  doCommit();  return result; }  private void doRollBack() {  try {   connectHolder.getConnection().rollback();  } catch (SQLException e) {   e.printStackTrace();  } finally {   connectHolder.cleanHolder();  } }  private void doCommit() {  try {   connectHolder.getConnection().commit();  } catch (SQLException e) {   e.printStackTrace();  } finally {   connectHolder.cleanHolder();  } }  private void completeTransactionAfterThrowing(Throwable throwable) {  if (es != null && es.length > 0) {   for (Class<? extends Throwable> e : es) {    if (e.isAssignableFrom(throwable.getClass())) {     doRollBack();    }   }  }  doCommit(); }}

2.4. 编写一个 Service

saveTest 方法调用了2个插入语句,同时声明了 @MyTransaction 事务注解,遇到 Exception 就进行回滚。

@Servicepublic class MyTransactionTest { @Autowired private DataSourceConnectHolder holder; // 一个事务中执行两个sql插入 @MyTransaction(rollbackFor = NullPointerException.class) public void saveTest(int id) {  save(id, "白菜Java自习室");  save(id + 10, "白菜Java自习室");  throw new RuntimeException(); } // 执行sql private void save(int id, String value) {  String sql = "insert into test values(?,?)";  Connection connection = holder.getConnection();  PreparedStatement stmt = null;  try {   stmt = connection.prepareStatement(sql);   stmt.setInt(1, id);   stmt.setString(2, value);   stmt.executeUpdate();  } catch (SQLException e) {   e.printStackTrace();  } }}

看完上述内容,你们掌握使用SpringBoot项目实现一个本地事务管理功能的方法了吗?如果还想学到更多技能或想了解更多相关内容,欢迎关注编程网行业资讯频道,感谢各位的阅读!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

使用SpringBoot项目实现一个本地事务管理功能

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

使用SpringBoot项目实现一个本地事务管理功能

使用SpringBoot项目实现一个本地事务管理功能?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。1. SpringBoot 事务一直在用 SpringBoot 中的 @Tra
2023-06-06

使用Java怎么实现一个记事本功能

今天就跟大家聊聊有关使用Java怎么实现一个记事本功能,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。源码: import java.awt.*; import java.awt.ev
2023-05-31

通过在android项目中使用MediaRecorder实现一个录音功能

这篇文章将为大家详细讲解有关通过在android项目中使用MediaRecorder实现一个录音功能,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。MainActivitypackage co
2023-05-31

在Android项目中使用OKHttp3怎么实现一个下载功能

这篇文章给大家介绍在Android项目中使用OKHttp3怎么实现一个下载功能,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。app Module下的build.gradle,代码如下apply plugin: com.
2023-05-31

在android项目中使用LinearLayoutManager实现一个一键返回顶部功能

本篇文章为大家展示了在android项目中使用LinearLayoutManager实现一个一键返回顶部功能,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。效果图要实现这种效果,有两点需要实现:1、控
2023-05-31

在Spring项目中使用 Hibernate如何实现一个分页功能

本篇文章给大家分享的是有关在Spring项目中使用 Hibernate如何实现一个分页功能,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。最关键的是运用Hibernate的que
2023-05-31

在springboot项目中使用quartz如何实现一个定时任务

今天就跟大家聊聊有关在springboot项目中使用quartz如何实现一个定时任务,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。spring支持多种定时任务的实现。我们来介绍下使用
2023-05-31

Android项目中使用SwipeRefreshLayout组件实现一个下拉刷新功能

这篇文章给大家介绍Android项目中使用SwipeRefreshLayout组件实现一个下拉刷新功能,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。SwipeRefreshLayout概述SwipeRefrshLayo
2023-05-31

Java项目中使用 Servlet怎么实现一个文件分享功能

本篇文章为大家展示了Java项目中使用 Servlet怎么实现一个文件分享功能,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。项目结构src com servletdemo Do
2023-05-31

在java项目中使用java.net.URLConnection实现一个发送HTTP请求功能

这期内容当中小编将会给大家带来有关在java项目中使用java.net.URLConnection实现一个发送HTTP请求功能,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。一、前言如何通过Java发送HT
2023-05-31

在BootStrap项目中使用Validator与My97实现一个日期校验功能

这期内容当中小编将会给大家带来有关在BootStrap项目中使用Validator与My97实现一个日期校验功能,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。首先my97的API中有自定义事件中有 onp
2023-05-31

在Java项目中使用WebUploader如何实现一个文件上传功能

本篇文章为大家展示了在Java项目中使用WebUploader如何实现一个文件上传功能,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。之前自己写小项目的时候也碰到过文件上传的问题,没有找到很好的解决方
2023-05-31

在Android项目中使用RecyclerView实现一个上拉加载更多功能

在Android项目中使用RecyclerView实现一个上拉加载更多功能?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。既然有刷新的时候有两种状态就要定义两个状
2023-05-31

在java项目中使用Demo如何实现一个文件上传功能

在java项目中使用Demo如何实现一个文件上传功能?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。说到文件上传我们要做到:1.引入两个包:commons-fileupload-
2023-05-31

java项目中使用datatables如何实现一个带条件查询功能

这篇文章给大家介绍java项目中使用datatables如何实现一个带条件查询功能,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。使用datatables自带后台查询 前台代码:
2023-05-31

在Java项目中使用递归如何实现一个文件读取功能

今天就跟大家聊聊有关在Java项目中使用递归如何实现一个文件读取功能,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。Java递归列出目录下全部文件 /** * 列出指定目录的全部内容
2023-05-31

如何在Java项目中使用OCR tesseract实现一个图文识别功能

如何在Java项目中使用OCR tesseract实现一个图文识别功能?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。代码:package com.zhy.test; im
2023-05-31

在Java项目中使用IO流实现一个音频剪切和拼接功能

今天就跟大家聊聊有关在Java项目中使用IO流实现一个音频剪切和拼接功能,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。需求: 使用IO流将指定目录下的若干个音频文件的高潮部分,进行剪
2023-05-31

在Java项目中使用fileupload组件如何实现一个文件上传功能

本篇文章给大家分享的是有关在Java项目中使用fileupload组件如何实现一个文件上传功能,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。使用fileupload组件的原因:
2023-05-31

在Android项目中使用RecyclerView实现一个上拉加载下拉刷新功能

这篇文章给大家介绍在Android项目中使用RecyclerView实现一个上拉加载下拉刷新功能,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。核心实现package com.example.fly.recyclervi
2023-05-31

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录