MyBatis异常体系中ErrorContext和ExceptionFactory原理分析
🎮 作者主页:点击
🎁 完整专栏和代码:点击
🏡 博客主页:点击
文章目录
- exceptions包
- 分包设计
- ExceptionFactory类
- 介绍
- 为什么使用工厂不是直接new呢?
- 【统一的异常处理机制】
- 【异常的封装与转化】
- 【异常上下文(ErrorContext)】
- 【扩展性】
- ErrorContext 的作用
- 概述
- 代码
- store()和recall()方法设计分析
- 为什么需要 store 和 recall?
exceptions包
exceptions包为 MyBatis定义了绝大多数异常类的父类,同时也提供了异常类的生产工厂


MyBatis中异常类类图
分包设计
通过 MyBatis异常类的类图还可以看出,众多的异常类并没有放在 exceptions包中,而是散落在其他各个包中。这涉及项目规划时的分包问题。通常,在规划一个项目的包结构时,可以按照以下两种方式进行包的划分。
按照类型方式划分,例如将所有的接口类放入一个包,将所有的 Controller类放入一个包。这种分类方式从类型上看更为清晰,但是会将完成同一功能的多个类分散在不同的包中,不便于模块化开发。
按照功能方式划分,例如将所有与加/解密有关的类放入一个包,将所有与 HTTP请求有关的类放入一个包。这种分类方式下,同一功能的类内聚性高,便于模块化开发,但会导致同一包内类的类型混乱
通常,在进行一个项目的包结构设计时会同时采用以上两种划分方式。exceptions包就是按照类型划分出来的,但也有许多异常类按照功能划分到了其他包中。MyBatis 中的包也是按照上述两种方式划分的,一类是按照类型划分出来的包,如exceptions包、annotations包;一类是按照功能划分出来的包,如 logging包、plugin包。
在项目设计和开发中,我们推荐优先将功能耦合度高的类放入按照功能划分的包中,而将功能耦合度低或供多个功能使用的类放入按照类型划分的包中。这种划分思想不仅可以用在包的划分上,类、方法、代码片段的组合与拆分等都可以参照这种思想。
ExceptionFactory类
介绍
该类是负责生产 Exception的工厂。ExceptionFactory类只有两个方法。构造方法由 private修饰,确保该方法无法在类的外部被调用,也就永远无法生成该类的实例。通常,会对一些工具类、工厂类等仅提供静态方法的类进行这样的设置,因为这些类不需要实例化就可以使用。wrapException方法就是 ExceptionFactory类提供的静态方法,它用来生成并返回一个RuntimeException对象。
@Overridepublic void rollback(boolean force) {try {executor.rollback(isCommitOrRollbackRequired(force));dirty = false;} catch (Exception e) {throw ExceptionFactory.wrapException("Error rolling back transaction. Cause: " + e, e);} finally {ErrorContext.instance().reset();}}
/*** @author Clinton Begin*/
public class ExceptionFactory {private ExceptionFactory() {// Prevent Instantiation}public static RuntimeException wrapException(String message, Exception e) {return new PersistenceException(ErrorContext.instance().message(message).cause(e).toString(), e);}}
为什么使用工厂不是直接new呢?
【统一的异常处理机制】
通过 ExceptionFactory.wrapException 来封装异常,MyBatis 可以提供一个统一的异常处理方式。这样做的好处是,在整个系统中无论遇到什么异常,都会通过 wrapException 进行统一包装,并且可以附带有一致的异常信息(比如 ErrorContext 中的消息和堆栈信息)。这种方式增强了异常的一致性,方便了异常的管理和日志记录。
【异常的封装与转化】
直接 new 一个异常是可以的,但 MyBatis 通过 wrapException 将异常转化为 PersistenceException,这种做法有助于将底层异常封装成一个业务层异常(如 PersistenceException),而不是让底层的异常类型暴露给上层调用者。这样上层代码就不需要关心底层的具体实现细节(如 SQLException 或其他数据库相关的异常),只需要处理通用的业务异常(PersistenceException)。这也是常见的“异常转译”或“异常抽象”设计模式。
【异常上下文(ErrorContext)】
ErrorContext.instance().message(message).cause(e).toString() 通过 ErrorContext 来追踪异常的上下文信息,提供更多的诊断信息。这对于调试和日志分析非常有用,可以提供异常发生时的详细背景、错误信息以及导致异常的根本原因。这使得开发者可以更容易地定位问题,提升系统的可维护性。
【扩展性】
使用 ExceptionFactory 封装异常,意味着未来如果需要改变异常的处理方式(例如,加入更多的异常日志、不同的异常类型等),只需要在 wrapException 方法中做修改,而不需要修改系统中每个直接抛出异常的地方。这提高了代码的可维护性和可扩展性。
通过 ExceptionFactory.wrapException 这样的设计,MyBatis 实现了异常的统一封装、转化和上下文管理,增强了系统的可维护性、可调试性和可扩展性。直接 new 一个异常虽然简单,但缺乏统一的异常处理和额外的上下文信息,容易导致异常管理混乱。
ErrorContext 的作用
概述
MyBatis 中的 ErrorContext 设计主要用于捕获和存储在执行 SQL 操作过程中发生的错误的相关上下文信息。它通过提供详细的错误上下文信息来帮助开发人员调试和排查问题,尤其是在与数据库交互时发生异常时。ErrorContext 类主要的作用是封装和管理错误相关的诊断信息,使得错误日志更加易于理解和追踪。
代码
public class ErrorContext {private static final String LINE_SEPARATOR = System.lineSeparator();private static final ThreadLocal<ErrorContext> LOCAL = ThreadLocal.withInitial(ErrorContext::new);private ErrorContext stored;private String resource;private String activity;private String object;private String message;private String sql;private Throwable cause;private ErrorContext() {}public static ErrorContext instance() {return LOCAL.get();}public ErrorContext store() {ErrorContext newContext = new ErrorContext();newContext.stored = this;LOCAL.set(newContext);return LOCAL.get();}public ErrorContext recall() {if (stored != null) {LOCAL.set(stored);stored = null;}return LOCAL.get();}public ErrorContext resource(String resource) {this.resource = resource;return this;}public ErrorContext activity(String activity) {this.activity = activity;return this;}public ErrorContext object(String object) {this.object = object;return this;}public ErrorContext message(String message) {this.message = message;return this;}public ErrorContext sql(String sql) {this.sql = sql;return this;}public ErrorContext cause(Throwable cause) {this.cause = cause;return this;}public ErrorContext reset() {resource = null;activity = null;object = null;message = null;sql = null;cause = null;LOCAL.remove();return this;}@Overridepublic String toString() {StringBuilder description = new StringBuilder();// messageif (this.message != null) {description.append(LINE_SEPARATOR);description.append("### ");description.append(this.message);}// resourceif (resource != null) {description.append(LINE_SEPARATOR);description.append("### The error may exist in ");description.append(resource);}// objectif (object != null) {description.append(LINE_SEPARATOR);description.append("### The error may involve ");description.append(object);}// activityif (activity != null) {description.append(LINE_SEPARATOR);description.append("### The error occurred while ");description.append(activity);}// sqlif (sql != null) {description.append(LINE_SEPARATOR);description.append("### SQL: ");description.append(sql.replace('\n', ' ').replace('\r', ' ').replace('\t', ' ').trim());}// causeif (cause != null) {description.append(LINE_SEPARATOR);description.append("### Cause: ");description.append(cause.toString());}return description.toString();}}
ErrorContext 的设计可以帮助 MyBatis 更好地追踪和记录执行过程中发生的异常和错误信息。它提供了关于 SQL 执行过程中各个步骤的详细信息,例如:当前正在执行的 SQL 语句、相关的映射文件、参数信息等,这些都可以帮助开发人员快速定位问题。
store()和recall()方法设计分析
在 MyBatis 中,private ErrorContext stored; 这行代码通常用于 保存当前线程的错误上下文。具体来说,stored 变量通常是一个 ErrorContext 类型的对象,它的作用是暂时存储和保存错误相关的信息,以便在整个操作过程中能够访问和更新。
private ErrorContext stored;public ErrorContext store() {ErrorContext newContext = new ErrorContext();newContext.stored = this;LOCAL.set(newContext);return LOCAL.get();}public ErrorContext recall() {if (stored != null) {LOCAL.set(stored);stored = null;}return LOCAL.get();}
store() 方法
• 这个方法的作用是 保存当前的错误上下文 到一个新的 ErrorContext 实例中,并将其设置为当前线程的错误上下文。
• newContext.stored = this:这行代码的意思是,将当前的 ErrorContext(即 this)存储到新创建的 newContext 中,这样可以在之后恢复原来的上下文。
• LOCAL.set(newContext):将新创建的上下文对象设置为当前线程的 ErrorContext。由于使用了 ThreadLocal,每个线程都会有独立的错误上下文。
• return LOCAL.get():返回当前线程的 ErrorContext(即 newContext)。
store() 方法一般在 操作开始时调用,用于 保存当前线程的错误上下文,并为后续的错误信息提供独立的上下文。比如,当处理一个新的数据库操作时,可能需要清空当前的错误上下文并开始一个新的上下文。在执行某个操作时,如果错误发生,则可以在新的上下文中记录错误信息。
recall() 方法通常用于 操作完成后,恢复原来的错误上下文,特别是在跨越多个操作的错误追踪中,恢复原来的上下文信息。比如,在处理完某个操作后,系统可能需要恢复到先前的错误上下文,以便继续处理其他操作或者记录原有的错误信息。
protected void generateKeys(Object parameter) {KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();ErrorContext.instance().store();keyGenerator.processBefore(executor, mappedStatement, null, parameter);ErrorContext.instance().recall();}
上面这段代码就是实际mybatis使用store和recall地方。
这段代码出现在 MyBatis 中,通常是在执行某些数据库操作时生成主键的过程,尤其是在插入操作中,涉及到主键生成的逻辑。generateKeys() 方法负责生成数据库操作后的主键,并确保在生成主键的过程中能够正确管理和追踪错误上下文。让我们逐行分析这段代码,理解其具体含义和作用:
protected void generateKeys(Object parameter) {// 获取与当前映射语句相关的 KeyGeneratorKeyGenerator keyGenerator = mappedStatement.getKeyGenerator();// 存储当前线程的错误上下文,防止在主键生成过程中出现错误时丢失上下文ErrorContext.instance().store();// 在执行主键生成前,调用 KeyGenerator 的处理方法keyGenerator.processBefore(executor, mappedStatement, null, parameter);// 恢复之前存储的错误上下文,确保其他操作的错误上下文不受影响ErrorContext.instance().recall();
}
为什么需要 store 和 recall?
在 MyBatis 中,ErrorContext 负责捕获和存储当前错误的上下文信息。在多步骤的操作中(如插入操作生成主键时),有时会因为主键生成的策略(如自增长主键)涉及到复杂的数据库操作,因此需要确保在生成主键之前,当前的错误上下文能够被保存,避免在主键生成时如果发生错误丢失上下文信息。
通过调用 store() 方法,保存当前错误上下文,并在执行主键生成逻辑后通过 recall() 恢复之前的上下文,能够确保错误信息在不同的操作之间得到正确的隔离和处理。这种方式使得 MyBatis 可以精确地记录并跟踪错误,尤其是在复杂的多步骤操作中,避免错误上下文的混乱。
上面方法的最外层是DefaultSqlSession#update(java.lang.String, java.lang.Object)
@Overridepublic int update(String statement, Object parameter) {try {dirty = true;MappedStatement ms = configuration.getMappedStatement(statement);return executor.update(ms, wrapCollection(parameter));} catch (Exception e) {throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);} finally {ErrorContext.instance().reset();}}
上面会捕获所有的异常并使用ExceptionFactory,而里面输出的信息就是ErrorContext存储的。
假设执行keyGenerator.processBefore(executor, mappedStatement, null, parameter);出现数据库异常了,那么此时可能抛出SQLException异常,而且在processBefore中会执行很多的自定义的操作,这些可能导致ErrorContext中存储的信息被修改了,那么之前在processBefore前保存的上下文信息就丢失了,这就会导致最终异常信息输出的时候只有processBefore的信息了,而丢失了原来的上下文信息,在使用了store方法后,先将这个前面的上下文信息保存,这样的话,假设在processBefore中ErrorContext信息被修改了,在最后的异常信息输出的时候,也可以通过store字段获取到。store() 和 recall() 确保了每个操作的错误上下文是独立的。在 generateKeys() 执行前,保存当前的错误上下文,在生成主键后恢复原有的上下文。这样,即使在执行过程中某个操作失败,错误信息也能清楚地区分开来,避免了上下文混乱。如果 generateKeys() 阶段失败,错误信息会指明是主键生成阶段的问题,而不是后续的更新操作。这样能够更容易地诊断和解决问题。会清楚知道错误发生在更新操作而不是主键生成阶段,从而能够更快定位问题。
通过使用 ErrorContext 进行错误上下文的存储和恢复,可以确保每个操作的错误信息都能得到独立处理,便于调试和问题的准确定位。
相关文章:
MyBatis异常体系中ErrorContext和ExceptionFactory原理分析
🎮 作者主页:点击 🎁 完整专栏和代码:点击 🏡 博客主页:点击 文章目录 exceptions包分包设计ExceptionFactory类介绍为什么使用工厂不是直接new呢?【统一的异常处理机制】【异常的封装与转化】【…...
WHLUG丨deepin、华中科技大学开放原子开源俱乐部、 RustSBI 和清华大学开源操作系统训练营共话开源新生代成长之路
2024年11月30日下午,由 deepin(深度)社区联合华中科技大学开放原子开源俱乐部、 RustSBI 开源社区和清华大学开源操作系统训练营共同举办的WHLUG(武汉Linux用户组)线下沙龙在华中科技大学成功举办。 本次活动聚集了50余…...
通过HTML Canvas 在图片上绘制文字
目录 前言 一、HTML Canvas 简介 二、准备工作 三、绘制图片 四、绘制文字 五、完整代码 效果演示: 前言 HTML canvas 为我们提供了无限的创意可能性。今天,我们就来探索一下如何通过 HTML canvas 将图片和文字绘制到图片上,创造出独特…...
C# 冒泡的算法
C# 冒泡的算法 public void BubbleSort(int[] arr) {int temp;for (int j 0; j < arr.Length - 2; j){for (int i 0; i < arr.Length - 2; i){if (arr[i] > arr[i 1]){temp arr[i 1];arr[i 1] arr[i];arr[i] temp;}}} }使用方法 int[] array new int[] { 5,…...
大数据项目-Django基于聚类算法实现的房屋售房数据分析及可视化系统
《[含文档PPT源码等]精品Django基于聚类算法实现的房屋售房数据分析及可视化系统》该项目含有源码、文档、PPT、配套开发软件、软件安装教程课程答疑等! 数据库管理工具:phpstudy/Navicat或者phpstudy/sqlyog 后台管理系统涉及技术: 后台使…...
AWS创建ec2实例并连接成功
aws创建ec2实例并连接 aws创建ec2并连接 1.ec2创建前准备 首先创建一个VPC隔离云资源并且有公有子网 2.创建EC2实例 1.启动新实例或者创建实例 2.创建实例名 3.选择AMI使用linux(HVM) 4.选择实例类型 5.创建密钥对下载到本地并填入密钥对名称 6.选择自己创建的VPC和公有子网…...
TypeScript 开始学习 -接触的新东西
目录 语言类型类型总览JavaScript 中的数据类型TypeScript 中的数据类型 定义类型断言索引签名泛型 语言类型 JS -> 弱类型 TS -> 强类型 TypeScript(TS)是一种强类型语言。这意味着在TypeScript中,变量和函数参数的类型必须在编译时…...
非对称任意进制转换器(安卓)
除了正常进制转换,还可以输入、输出使用不同的数字符号,达成对数值进行加密的效果 点我下载APK安装包 使用unity开发。新建一个c#代码文件,把代码覆盖进去,再把代码文件添加给main camera即可。 using System.Collections; usin…...
【优选算法篇】寻找隐藏的宝藏:用二分查找打开算法世界的大门(上篇)
文章目录 须知 💬 欢迎讨论:如果你在学习过程中有任何问题或想法,欢迎在评论区留言,我们一起交流学习。你的支持是我继续创作的动力! 👍 点赞、收藏与分享:觉得这篇文章对你有帮助吗࿱…...
基于Vue实现的移动端手机商城项目 电商购物网站 成品源码
📂文章目录 一、📔网站题目 二、✍️网站描述 三、📚网站介绍 四、🌐网站演示 📸部分截图 🎬视频演示 五、⚙️网站代码 🧱项目结构 💒vue代码预览 六、🔧完整…...
C语言:数组
数组 数组的概念 引例 如果我们要在程序中表示一个学生的成绩,我们会使用一个int来表示,如:int score。假如我们要在程序中表示一组成绩,此时我们所学的常规的数据类型就无法再表示,这个时候我们就需要使用一种新的…...
时间序列预测之FiLM
没错,就是看电影 文章目录 前言1. 问题描述2. 创新之处3. 贡献 一、时间序列在legende - fourier域的表示1. 勒让德投影2. 傅里叶变换 二、 模型结构1. LPU: Legendre Projection Unit2. FEL: Frequency Enhanced Layer3. 多尺度专家机制的混合 二、实验结果长时预测…...
【机器学习】窥数据之序,悟算法之道:机器学习的初心与远方
文章目录 机器学习入门:从零开始学习基础与应用前言第一部分:什么是机器学习?1.1 机器学习的定义1.1.1 举个例子:垃圾邮件分类器 1.2 机器学习的核心思想1.2.1 数据驱动的模式提取1.2.2 为什么机器学习比传统方法更灵活࿱…...
OpenCL介绍
OpenCL(Open Computing Language)详解 OpenCL 是一个开源的框架,用于编写在异构平台(包括中央处理单元(CPU)、图形处理单元(GPU)、数字信号处理器(DSP)和其他…...
「Mac畅玩鸿蒙与硬件42」UI互动应用篇19 - 数字键盘应用
本篇将带你实现一个数字键盘应用,支持用户通过点击数字键输入数字并实时更新显示内容。我们将展示如何使用按钮组件和状态管理来实现一个简洁且实用的数字键盘。 关键词 UI互动应用数字键盘按钮组件状态管理用户交互 一、功能说明 数字键盘应用将实现以下功能&…...
【前端知识】npm命令行详细说明
npm命令行详细说明 概述一、定义与功能二、基本命令三、配置文件与注册表四、应用场景五、高级特性 环境设置1. 设置镜像源2. 配置全局依赖存储路径3. 配置缓存路径4. 查看所有配置5. 清除缓存6. 升级npm版本 npm组件打包1. 初始化项目2. 安装依赖3. 构建脚本4. 打包项目5. 发布…...
Python网络爬虫技术详解与实践案例
Python网络爬虫技术详解与实践案例 在大数据和人工智能盛行的今天,数据的获取与分析成为许多项目和业务的关键。网络爬虫作为一种自动化的数据采集工具,广泛应用于数据挖掘、市场分析、情报收集等领域。本文将详细介绍Python网络爬虫的基本概念、工作流程、进阶技巧,并附上…...
【遥感目标检测综述】【GRSS】遥感目标检测与深度学习的相遇:挑战与进展的元综述
Remote Sensing Object Detection Meets Deep Learning: A Meta-review of Challenges and Advances 遥感目标检测与深度学习的相遇:挑战与进展的元综述 论文链接 0.论文摘要和作者信息 摘要 遥感目标检测(RSOD)是遥感领域最基…...
【大数据技术基础】 课程 第3章 Hadoop的安装和使用 大数据基础编程、实验和案例教程(第2版)
第3章 Hadoop的安装和使用 3.1 Hadoop简介 Hadoop是Apache软件基金会旗下的一个开源分布式计算平台,为用户提供了系统底层细节透明的分布式基础架构。Hadoop是基于Java语言开发的,具有很好的跨平台特性,并且可以部署在廉价的计算机集群中。H…...
【机器学习】机器学习的基本分类-监督学习-决策树-C4.5 算法
C4.5 是由 Ross Quinlan 提出的决策树算法,是对 ID3 算法的改进版本。它在 ID3 的基础上,解决了以下问题: 处理连续型数据:支持连续型特征,能够通过划分点将连续特征离散化。处理缺失值:能够在特征值缺失的…...
19c补丁后oracle属主变化,导致不能识别磁盘组
补丁后服务器重启,数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后,存在与用户组权限相关的问题。具体表现为,Oracle 实例的运行用户(oracle)和集…...
React Native 导航系统实战(React Navigation)
导航系统实战(React Navigation) React Navigation 是 React Native 应用中最常用的导航库之一,它提供了多种导航模式,如堆栈导航(Stack Navigator)、标签导航(Tab Navigator)和抽屉…...
前端倒计时误差!
提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...
遍历 Map 类型集合的方法汇总
1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...
【项目实战】通过多模态+LangGraph实现PPT生成助手
PPT自动生成系统 基于LangGraph的PPT自动生成系统,可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析:自动解析Markdown文档结构PPT模板分析:分析PPT模板的布局和风格智能布局决策:匹配内容与合适的PPT布局自动…...
三体问题详解
从物理学角度,三体问题之所以不稳定,是因为三个天体在万有引力作用下相互作用,形成一个非线性耦合系统。我们可以从牛顿经典力学出发,列出具体的运动方程,并说明为何这个系统本质上是混沌的,无法得到一般解…...
Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)
参考官方文档:https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java(供 Kotlin 使用) 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
在QWebEngineView上实现鼠标、触摸等事件捕获的解决方案
这个问题我看其他博主也写了,要么要会员、要么写的乱七八糟。这里我整理一下,把问题说清楚并且给出代码,拿去用就行,照着葫芦画瓢。 问题 在继承QWebEngineView后,重写mousePressEvent或event函数无法捕获鼠标按下事…...
【C++进阶篇】智能指针
C内存管理终极指南:智能指针从入门到源码剖析 一. 智能指针1.1 auto_ptr1.2 unique_ptr1.3 shared_ptr1.4 make_shared 二. 原理三. shared_ptr循环引用问题三. 线程安全问题四. 内存泄漏4.1 什么是内存泄漏4.2 危害4.3 避免内存泄漏 五. 最后 一. 智能指针 智能指…...
