当前位置: 首页 > news >正文

手写MyBatis第102弹:MapperBuilder与MapperAnnotationBuilder的双重解析机制深度剖析

MyBatis Mapper配置解析深度解密:XML与注解的双重解析引擎

「MyBatis Mapper解析内核揭秘:XMLMapperBuilder与MapperAnnotationBuilder的双重解析机制深度剖析」

Mapper配置解析:MyBatis的核心生命力

在MyBatis的整体架构中,Mapper配置解析是整个框架最为核心和复杂的部分。它承担着将开发者定义的接口和SQL映射转化为可执行代码的重任。理解XMLMapperBuilderMapperAnnotationBuilder的工作原理,就等于掌握了MyBatis的灵魂所在。

  🥂(❁´◡`❁)您的点赞👍➕评论📝➕收藏⭐是作者创作的最大动力🤞

💖📕🎉🔥 支持我:点赞👍+收藏⭐️+留言📝欢迎留言讨论

🔥🔥🔥(源码 + 调试运行 + 问题答疑)

🔥🔥🔥  有兴趣可以联系我。文末有免费源码

免费获取源码。

更多内容敬请期待。如有需要可以联系作者免费送

更多源码定制,项目修改,项目二开可以联系作者
点击可以进行搜索(每人免费送一套代码):千套源码目录(点我)

2025元旦源码免费送(点我)

我们常常在当下感到时间慢,觉得未来遥远,但一旦回头看,时间已经悄然流逝。对于未来,尽管如此,也应该保持一种从容的态度,相信未来仍有许多可能性等待着我们。

XMLMapperBuilder:XML配置的解析引擎

解析入口与初始化过程

XMLMapperBuilder是处理Mapper XML文件的核心类,其构造过程体现了资源定位和依赖注入的设计思想:

 public class XMLMapperBuilder extends BaseBuilder {public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {this(configuration, new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()), resource, sqlFragments);}private XMLMapperBuilder(Configuration configuration, XPathParser parser, String resource, Map<String, XNode> sqlFragments) {super(configuration);this.builderAssistant = new MapperBuilderAssistant(configuration, resource);this.parser = parser;this.resource = resource;this.sqlFragments = sqlFragments;}}

关键组件解析:

  • XPathParser:基于XPath的XML解析器,负责将XML转换为DOM树

  • MapperBuilderAssistant:构建助手,负责具体的Mapper组件构建

  • sqlFragments:SQL片段缓存,支持<sql>标签的重用

核心解析流程分析

parse()方法是XMLMapperBuilder的入口点,它按照固定的顺序解析Mapper文件的各个部分:

 public void parse() {// 检查是否已经加载过该资源,避免重复解析if (!configuration.isResourceLoaded(resource)) {// 解析mapper根节点configurationElement(parser.evalNode("/mapper"));// 标记资源已加载configuration.addLoadedResource(resource);// 绑定Mapper接口bindMapperForNamespace();}// 处理未解析的ResultMap和CacheRefparsePendingResultMaps();parsePendingCacheRefs();parsePendingStatements();}

configurationElement()方法体现了XML配置的完整解析顺序:

 private void configurationElement(XNode context) {try {// 1. 解析namespaceString namespace = context.getStringAttribute("namespace");if (namespace == null || namespace.equals("")) {throw new BuilderException("Mapper's namespace cannot be empty");}builderAssistant.setCurrentNamespace(namespace);// 2. 解析cache-refcacheRefElement(context.evalNode("cache-ref"));// 3. 解析cache(二级缓存配置)cacheElement(context.evalNode("cache"));// 4. 解析resultMap(结果映射)resultMapElements(context.evalNodes("resultMap"));// 5. 解析sql(可重用的SQL片段)sqlElement(context.evalNodes("sql"));// 6. 解析CRUD语句(最核心的部分)buildStatementFromContext(context.evalNodes("select"),context.evalNodes("insert"),context.evalNodes("update"),context.evalNodes("delete"));} catch (Exception e) {throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);}}
SQL语句解析的深度机制

buildStatementFromContext()方法负责处理具体的SQL语句节点,这是MyBatis最核心的功能:

 private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {for (XNode context : list) {final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);try {// 解析单个SQL语句节点statementParser.parseStatementNode();} catch (IncompleteElementException e) {// 处理解析依赖问题configuration.addIncompleteStatement(statementParser);}}}

XMLStatementBuilder.parseStatementNode()的关键步骤:

 public void parseStatementNode() {// 1. 获取语句属性String id = context.getStringAttribute("id");String databaseId = context.getStringAttribute("databaseId");// 2. 验证databaseId匹配if (!databaseIdMatchesCurrent(id, databaseId, builderAssistant.getCurrentNamespace())) {return;}// 3. 解析各种属性Integer fetchSize = context.getIntAttribute("fetchSize");Integer timeout = context.getIntAttribute("timeout");String parameterMap = context.getStringAttribute("parameterMap");String parameterType = context.getStringAttribute("parameterType");Class<?> parameterTypeClass = resolveClass(parameterType);String resultMap = context.getStringAttribute("resultMap");String resultType = context.getStringAttribute("resultType");String lang = context.getStringAttribute("lang");String resultSetType = context.getStringAttribute("resultSetType");// 4. 解析SQL命令类型String nodeName = context.getNode().getNodeName();SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));// 5. 判断是否为Select语句boolean isSelect = sqlCommandType == SqlCommandType.SELECT;boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);boolean useCache = context.getBooleanAttribute("useCache", isSelect);// 6. 解析SQL源SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);// 7. 构建MappedStatementbuilderAssistant.addMappedStatement(id, sqlSource, sqlCommandType, /* 其他参数... */);}

MapperAnnotationBuilder:注解配置的解析引擎

注解解析的架构设计

MapperAnnotationBuilder专门处理基于注解的Mapper配置,它采用了一种完全不同的解析策略:

 public class MapperAnnotationBuilder {private final Configuration configuration;private final MapperBuilderAssistant assistant;private final Class<?> type;public MapperAnnotationBuilder(Configuration configuration, Class<?> type) {this.configuration = configuration;this.assistant = new MapperBuilderAssistant(configuration, type.getName());this.type = type;}public void parse() {String resource = type.getName().replace('.', '/') + ".java (best guess)";// 检查资源是否已加载if (!configuration.isResourceLoaded(resource)) {// 解析注解配置loadXmlResource();configuration.addLoadedResource(resource);assistant.setCurrentNamespace(type.getName());parseCache();parseCacheRef();// 解析接口方法Method[] methods = type.getMethods();for (Method method : methods) {try {if (!method.isBridge()) {parseStatement(method);}} catch (IncompleteElementException e) {configuration.addIncompleteMethod(new MethodResolver(this, method));}}}parsePendingMethods();}}
注解解析的核心逻辑

parseStatement()方法是注解解析的核心,它需要处理各种SQL注解:

 void parseStatement(Method method) {// 获取方法参数类型Class<?> parameterTypeClass = getParameterType(method);// 获取语言驱动LanguageDriver languageDriver = getLanguageDriver(method);// 解析SQL注解SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);if (sqlSource == null) {return;}// 解析其他注解属性SqlCommandType sqlCommandType = getSqlCommandType(method);boolean isSelect = sqlCommandType == SqlCommandType.SELECT;// 解析Options注解Options options = method.getAnnotation(Options.class);final String mappedStatementId = type.getName() + "." + method.getName();// 构建MappedStatementassistant.addMappedStatement(mappedStatementId,sqlSource,sqlCommandType,/* 其他参数... */);}
多注解支持的实现机制

MyBatis支持多种SQL注解,每种注解都有特定的解析逻辑:

 private SqlSource getSqlSourceFromAnnotations(Method method, Class<?> parameterType, LanguageDriver languageDriver) {try {// 检查@Select注解Class<? extends Annotation> sqlAnnotationType = getSqlAnnotationType(method);Class<? extends Annotation> sqlProviderAnnotationType = getSqlProviderAnnotationType(method);if (sqlAnnotationType != null) {if (sqlProviderAnnotationType != null) {throw new BindingException("You cannot supply both a static SQL and SqlProvider method.");}// 解析静态SQL注解Annotation sqlAnnotation = method.getAnnotation(sqlAnnotationType);final String[] strings = (String[]) sqlAnnotation.getClass().getMethod("value").invoke(sqlAnnotation);return buildSqlSourceFromStrings(strings, parameterType, languageDriver);} else if (sqlProviderAnnotationType != null) {// 解析SQL Provider注解return buildSqlSourceFromSqlProvider(method, languageDriver);}return null;} catch (Exception e) {throw new BuilderException("Could not find value method on SQL annotation.  Cause: " + e, e);}}

支持的注解类型包括:

  • @Select, @Insert, @Update, @Delete:静态SQL注解

  • @SelectProvider, @InsertProvider, @UpdateProvider, @DeleteProvider:动态SQL提供者

  • @Options:语句选项配置

  • @ResultMap:结果映射引用

  • @ResultType:结果类型指定

双重解析机制的协同工作

XML与注解的优先级处理

MyBatis允许同时使用XML和注解配置,框架需要处理两者的优先级和冲突:

public void parse() {// 先尝试加载XML配置String resource = type.getName().replace('.', '/') + ".xml";InputStream inputStream = Resources.getResourceAsStream(resource);if (inputStream != null) {// 如果存在XML配置,优先使用XMLXMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), resource, configuration.getSqlFragments());xmlParser.parse();} else {// 否则使用注解配置parseAnnotation();}
}
冲突解决策略

当XML和注解配置冲突时,MyBatis采用明确的解决策略:

  1. 同名校验:相同的方法名在XML和注解中不能重复定义

  2. XML优先:如果同时存在,XML配置会覆盖注解配置

  3. 运行时验证:在Mapper注册阶段进行完整性检查

解析过程中的高级特性

延迟解析机制

MyBatis采用延迟解析策略来处理配置间的依赖关系:

private void parsePendingResultMaps() {Collection<ResultMapResolver> incompleteResultMaps = configuration.getIncompleteResultMaps();synchronized (incompleteResultMaps) {Iterator<ResultMapResolver> iter = incompleteResultMaps.iterator();while (iter.hasNext()) {try {iter.next().resolve();iter.remove();} catch (IncompleteElementException e) {// 仍然无法解析,继续等待}}}
}
碎片化SQL的管理

<sql>片段的解析和管理体现了代码复用的设计思想:

private void sqlElement(List<XNode> list) {if (configuration.getDatabaseId() != null) {sqlElement(list, configuration.getDatabaseId());}sqlElement(list, null);
}private void sqlElement(List<XNode> list, String requiredDatabaseId) {for (XNode context : list) {String databaseId = context.getStringAttribute("databaseId");String id = context.getStringAttribute("id");id = builderAssistant.applyCurrentNamespace(id, false);if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {// 缓存SQL片段供后续使用sqlFragments.put(id, context);}}
}

性能优化与缓存策略

解析结果缓存

为了避免重复解析,MyBatis实现了多级缓存机制:

 public class Configuration {// 已加载的资源缓存protected final Set<String> loadedResources = new HashSet<>();// Mapper注册表缓存protected final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();// MappedStatement缓存protected final Map<String, MappedStatement> mappedStatements = new StrictMap<>("Mapped Statements collection");// 缓存配置protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");}
懒加载与按需解析

MyBatis采用懒加载策略,只有在真正使用时才进行完整解析:

 public class MapperRegistry {public <T> T getMapper(Class<T> type, SqlSession sqlSession) {final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);if (mapperProxyFactory == null) {throw new BindingException("Type " + type + " is not known to the MapperRegistry.");}try {// 动态代理创建Mapper实例return mapperProxyFactory.newInstance(sqlSession);} catch (Exception e) {throw new BindingException("Error getting mapper instance. Cause: " + e, e);}}}

总结

XMLMapperBuilderMapperAnnotationBuilder共同构成了MyBatis Mapper配置解析的双重引擎,它们分别针对不同的配置风格提供了完整的解决方案。通过深入分析这两个核心类的实现,我们可以学到:

  1. 架构设计智慧:如何设计可扩展的解析器架构

  2. 代码复用策略:通过Builder模式实现复杂对象的逐步构建

  3. 异常处理机制:优雅处理解析过程中的依赖和异常

  4. 性能优化思想:通过缓存和懒加载提升框架性能

理解这些底层机制,不仅有助于我们更好地使用MyBatis,更能提升我们的系统设计能力和代码质量意识。这种从使用者到理解者的转变,是每个开发者技术成长的重要里程碑。

🥂(❁´◡`❁)您的点赞👍➕评论📝➕收藏⭐是作者创作的最大动力🤞

💖📕🎉🔥 支持我:点赞👍+收藏⭐️+留言📝欢迎留言讨论

🔥🔥🔥(源码 + 调试运行 + 问题答疑)

🔥🔥🔥  有兴趣可以联系我。文末有免费源码

💖学习知识需费心,
📕整理归纳更费神。
🎉源码免费人人喜,
🔥码农福利等你领!

💖常来我家多看看,
📕网址:扣棣编程
🎉感谢支持常陪伴,
🔥点赞关注别忘记!

💖山高路远坑又深,
📕大军纵横任驰奔,
🎉谁敢横刀立马行?
🔥唯有点赞+关注成!

往期文章推荐:

基于Springboot + vue实现的学生宿舍信息管理系统
免费获取宠物商城源码--SpringBoot+Vue宠物商城网站系统 
【2025小年源码免费送】

⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇点击此处获取源码⬇⬇⬇⬇⬇⬇⬇⬇⬇

http://www.dtcms.com/a/470560.html

相关文章:

  • Java知识点储备
  • C++ : AVL 树之 左右双旋(第三章)
  • 查询类网站开发建设网站公司怎么收费
  • 电影发布网站模板WordPress云虚拟空间
  • Android获取外部存储目录
  • AUTOSAR模块架构
  • 简单易做的网站一级域名二级域名区别
  • 一个可计算宇宙模型:热力学规则驱动量子化弹性两层底空间演化的可编程物理模拟自动机设计-从量子过程到数值相对论模拟
  • 编程语言的选择策略:从C语言的OOP到AI与GUI开发的全方位对比
  • 网站 案例展示泗洪做网站公司
  • 做竞拍网站合法吗有谁认识做微网站的
  • 运行smolvlm解析视频
  • 【力扣】hot100系列(三)链表(一)(图示+多解法+时间复杂度分析)
  • 【Linux】linux基础指令入门(1)
  • 广东网站制作公司校园网站建设结论
  • 广州商城型网站福州核酸检测最新通知
  • 关于 旁注
  • mysql数据库介绍
  • Java程序设计
  • JavaScript从入门到实战 (1):JS 入门第一步:它是什么?能做什么?环境怎么搭?
  • Shell 中 $@ 与 $* 的核心区别:双引号包裹下的关键差异解析
  • 重庆网站seo网站外链购买平台
  • 乐清做网站哪家好汕头第一网e京网
  • 为什么ES中不推荐使用wildcard查询
  • 怎么叫人做网站高端的网站推广
  • ICT 数字测试原理 18 - -VCL如何对设备进行预处理
  • 19-基于STM32的人体体征监测系统设计与实现
  • 第12讲:深入理解指针(2)——指针的“安全锁”与“传址魔法”
  • 小企业网站制作wordpress 搭建个人博客
  • 企石镇做网站中国建筑装饰网饶明富