ToB企服应用市场:ToB评测及商务社交产业平台

标题: 第 3 章 核心处置惩罚层(上) [打印本页]

作者: 东湖之滨    时间: 2025-1-23 11:07
标题: 第 3 章 核心处置惩罚层(上)

3.1 MyBatis初始化

MyBatis 初始化过程中,除了会读取 mybatis-config.xml 配置文件以及映射配置文件,还会加载配置文件指定的类,处置惩罚类中的注解,创建一些配置对象,终极完成框架中各个模块的初始化。
3.1.1 建造者模式

建造者模式(也被称为“天生器模式”)将一个复杂对象的构建过程与它的表现分离,从而使得同样的构建过程可以创建差别的表现。用户只需要了解复杂对象的范例和内容,而无须关注复杂对象的具体构造过程。

重要脚色

长处:

3.1.2 BaseBuilder

MyBatis 初始化的重要工作是加载并剖析 mybatis-config.xml 配置文件,映射文件以及干系的注解信息。其入口是 SqlSessionFactoryBuilder.build() 方法
  1. public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
  2.         try {
  3.                 // 读取配置文件
  4.                 XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
  5.                 // 解析配置文件得到 Configuration 对象,创建 DefaultSqlSessionFactory 对象
  6.                 return build(parser.parse());
  7.         } catch (Exception e) {
  8.                 throw ExceptionFactory.wrapException("Error building SqlSession.", e);
  9.         } finally {
  10.                 ErrorContext.instance().reset();
  11.                 try {
  12.                         reader.close();
  13.                 } catch (IOException e) {
  14.                         // Intentionally ignore. Prefer previous error.
  15.                 }
  16.         }
  17. }
复制代码
此中用来剖析 XML 的 XMLConfigBuilder,就是继续自 BaseBuilder,其子类如图

  1. public abstract class BaseBuilder {
  2.         // Configuration 是 MyBatis 初始化过程的核心对象,MyBatis 中几乎全部的配置信息都会保存到 Configuration 对象中
  3.         protected final Configuration configuration;
  4.    
  5.         // mybatis-config.xml 配置文件中的别名标签
  6.         protected final TypeAliasRegistry typeAliasRegistry;
  7.    
  8.         // mybatis-config.xml 配置文件中使用 <typeHandler> 标签添加的 TypeHandler
  9.         protected final TypeHandlerRegistry typeHandlerRegistry;
  10.         public BaseBuilder(Configuration configuration) {
  11.                 this.configuration = configuration;
  12.                 this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
  13.                 this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
  14.         }
  15. }
复制代码
3.1.3 XMLConfigBuilder

XMLConfigBuilder 重要负责剖析 mybatis-config.xml 配置文件
  1. public class XMLConfigBuilder extends BaseBuilder {
  2.         // 标识是否已经解析过 mybatis-config.xml 配置文件
  3.         private boolean parsed;
  4.        
  5.         // 用于解析 mybatis-config.xml 配置文件的 XPathParser 对象
  6.         private final XPathParser parser;
  7.        
  8.         // 标识 <environment> 配置的名称,默认读取 <environment> 标签的 default 属性
  9.         private String environment;
  10.        
  11.         // ReflectorFactory 负责创建和缓存 Reflector 对象
  12.         private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
  13. }
复制代码
XMLConfigBuilder 通过调用 parseConfiguration 方法实现剖析功能,具体实现如下
  1. private void parseConfiguration(XNode root) {
  2.         try {
  3.                 // 解析 <properties> 标签
  4.                 propertiesElement(root.evalNode("properties"));
  5.                 // 解析 <settings> 标签
  6.                 Properties settings = settingsAsProperties(root.evalNode("settings"));
  7.                 // 设置 vfsImpl 字段
  8.                 loadCustomVfs(settings);
  9.                 // 加载日志实现类
  10.                 loadCustomLogImpl(settings);
  11.                 // 解析 <typeAliases> 标签
  12.                 typeAliasesElement(root.evalNode("typeAliases"));
  13.                 // 解析 <plugins> 标签
  14.                 pluginElement(root.evalNode("plugins"));
  15.                 // 解析 <objectFactory> 标签
  16.                 objectFactoryElement(root.evalNode("objectFactory"));
  17.                 // 解析 <objectWrapperFactory> 标签
  18.                 objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
  19.                 // 解析 <reflectorFactory> 标签
  20.                 reflectorFactoryElement(root.evalNode("reflectorFactory"));
  21.                 // 设置 settings 字段
  22.                 settingsElement(settings);
  23.                 // 解析 <environments> 标签
  24.                 environmentsElement(root.evalNode("environments"));
  25.                 // 解析 <databaseIdProvider> 标签
  26.                 databaseIdProviderElement(root.evalNode("databaseIdProvider"));
  27.                 // 解析 <typeHandlers> 标签
  28.                 typeHandlerElement(root.evalNode("typeHandlers"));
  29.                 // 解析 <mappers> 标签
  30.                 mapperElement(root.evalNode("mappers"));
  31.         } catch (Exception e) {
  32.                 throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  33.         }
  34. }
复制代码
3.1.4 XMLMapperBuilder

XMLMapperBuilder 负责剖析映射配置文件,parse 方法是剖析入口
  1. public class XMLMapperBuilder extends BaseBuilder {
  2.         private final XPathParser parser;
  3.         private final MapperBuilderAssistant builderAssistant;
  4.         private final Map<String, XNode> sqlFragments;
  5.         private final String resource;
  6.    
  7. }
复制代码
parse
  1. public void parse() {
  2.     // 判断是否已经加载过该映射文件
  3.     if (!configuration.isResourceLoaded(resource)) {
  4.         // 处理<mapper>节点
  5.         configurationElement(parser.evalNode("/mapper"));
  6.         // 将resource添加到Configuration.loadedResources集合中,它是一个 HashSet<String> 集合,其中存储了已经加载过的映射文件的路径
  7.         configuration.addLoadedResource(resource);
  8.         // 注册 Mapper 接口
  9.         bindMapperForNamespace();
  10.     }
  11.     // 解析 configurationElement 方法中解析失败的<resultMap>节点
  12.     parsePendingResultMaps();
  13.     // 解析 configurationElement 方法中解析失败的<cache-ref>节点
  14.     parsePendingCacheRefs();
  15.     // 解析 configurationElement 方法中解析失败的 SQL 语句节点
  16.     parsePendingStatements();
  17. }
复制代码
configurationElement
  1. private void configurationElement(XNode context) {
  2.     try {
  3.         // 获取 namespace 属性
  4.         String namespace = context.getStringAttribute("namespace");
  5.         if (namespace == null || namespace.isEmpty()) {
  6.             throw new BuilderException("Mapper's namespace cannot be empty");
  7.         }
  8.         builderAssistant.setCurrentNamespace(namespace);
  9.         // 解析 <cache-ref> 节点
  10.         cacheRefElement(context.evalNode("cache-ref"));
  11.         // 解析 <cache> 节点
  12.         cacheElement(context.evalNode("cache"));
  13.         // 解析 <parameterMap> 节点,已废弃,不推荐使用
  14.         parameterMapElement(context.evalNodes("/mapper/parameterMap"));
  15.         // 解析 <resultMap> 节点
  16.         resultMapElements(context.evalNodes("/mapper/resultMap"));
  17.         // 解析 <sql> 节点
  18.         sqlElement(context.evalNodes("/mapper/sql"));
  19.         // 解析 <select>、<insert>、<update>、<delete> 节点
  20.         buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
  21.     } catch (Exception e) {
  22.         throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
  23.     }
  24. }
复制代码
3.1.5 XMLStatementBuilder

XMLStatementBuilder 负责剖析 SQL 节点界说的 SQL 语句。也就是上述 configurationElement 方法的末了一步 buildStatementFromContext 实际上是调用 XMLStatementBuilder 实现的。
  1. public class XMLStatementBuilder extends BaseBuilder {
  2.         private final MapperBuilderAssistant builderAssistant;
  3.         private final XNode context;
  4.         private final String requiredDatabaseId;
  5. }
复制代码
MyBatis 使用 SqlSource 接口表现映射文件或注解中界说的 SQL 语句
  1. public interface SqlSource {
  2.         // 根据映射文件或注解描述的 SQL 语句,以及传入的参数,返回可执行的 SQL
  3.         BoundSql getBoundSql(Object parameterObject);
  4. }
复制代码
MyBatis 使用 MappedStatement 表现映射配置文件中界说的 SQL 节点
  1. public final class MappedStatement {
  2.     // 节点中的 id 属性(包括命名空间前缀)
  3.     private String resource;
  4.    
  5.     // 对应一条 SQL 语句
  6.     private SqlSource sqlSource;
  7.    
  8.     // SQL 的类型,INSERT、UPDATE、DELETE、SELECT
  9.     private SqlCommandType sqlCommandType;
  10.    
  11.     // ……
  12.   
  13. }
复制代码
XMLStatementBuilder 的 parseStatementNode 方法是剖析 SQL 节点的入口函数
  1. public void parseStatementNode() {
  2.     // 获取 SQL 节点的 id 和 databaseId 属性,若 databaseId 属性值与当前使用的数据库不匹配,则不加载该 SQL 节点;
  3.     // 若存在相同 id 且 databaseId 属性值不为空的 SQL 节点,则不加载该 SQL 节点;
  4.     String id = context.getStringAttribute("id");
  5.     String databaseId = context.getStringAttribute("databaseId");
  6.     if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
  7.         return;
  8.     }
  9.     // 获取 SQL 节点的 nodeName 属性值,即 SQL 类型,如 SELECT、INSERT、UPDATE、DELETE 等;
  10.     String nodeName = context.getNode().getNodeName();
  11.     SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
  12.     // 在解析 SQL 节点之前,先解析 <include> 节点,将 <include> 节点中的 SQL 片段合并到当前 SQL 节点中;
  13.     XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
  14.     includeParser.applyIncludes(context.getNode());
  15.     // 处理 <selectKey> 节点,该节点用于生成主键;
  16.     processSelectKeyNodes(id, parameterTypeClass, langDriver);
  17.     // 完成 SQL 节点的解析,后面单独分析
  18. }
复制代码
3.1.6 绑定Mapper接口

每个映射配置文件的定名空间可以绑定一个 Mapper 接口,并注册到 MapperRegistry 中。通过 XMLMapperBuilder.bindMapperForNamespace() 实现映射配置文件与对应 Mapper 接口的绑定。
  1. private void bindMapperForNamespace() {
  2.     // 获取映射配置文件的命名空间
  3.     String namespace = builderAssistant.getCurrentNamespace();
  4.     if (namespace != null) {
  5.         Class<?> boundType = null;
  6.         try {
  7.             // 解析命名空间对应的类型
  8.             boundType = Resources.classForName(namespace);
  9.         } catch (ClassNotFoundException e) {
  10.             // ignore, bound type is not required
  11.         }
  12.         // 是否已经加载了 boundType 接口
  13.         if (boundType != null && !configuration.hasMapper(boundType)) {
  14.             // 追加 namespace 前缀,并添加到 configuration.loadedResources 集合中
  15.             configuration.addLoadedResource("namespace:" + namespace);
  16.             // 调用 MapperRegistry.addMapper() 方法,注册 boundType 接口
  17.             configuration.addMapper(boundType);
  18.         }
  19.     }
  20. }
复制代码
3.1.7 处置惩罚incomplete*集合

XMLMapperBuilder.configurationElement() 方法在剖析映射配置文件前面的节点时,可能会引用背面还未剖析的节点,导致剖析失败并抛出 IncompleteElementException。
MyBatis 会捕获这些异常并做针对性处置惩罚,前面提到的 parsePendingResultMaps()、parsePendingCacheRefs()、parsePendingStatements() 三个方法就是为此而产生的。
  1. private void parsePendingStatements() {
  2.     // 获取 Configuration.incompleteStatements 集合
  3.     Collection<XMLStatementBuilder> incompleteStatements = configuration.getIncompleteStatements();
  4.     // 加锁,避免并发问题
  5.     synchronized (incompleteStatements) {
  6.         // 遍历 incompleteStatements 集合
  7.         Iterator<XMLStatementBuilder> iter = incompleteStatements.iterator();
  8.         while (iter.hasNext()) {
  9.             try {
  10.                 // 重新解析 Statement 节点
  11.                 iter.next().parseStatementNode();
  12.                 iter.remove();
  13.             } catch (IncompleteElementException e) {
  14.                 // Statement is still missing a resource...
  15.             }
  16.         }
  17.     }
  18. }
复制代码
3.2 SqlNode&SqlSource

映射配置文件中界说的 SQL 会被剖析成 SqlSource 对象,SQL 语句中界说的动态 SQL 节点、文本节点等,则由 SqlNode 接口的相应实现表现。
  1. public interface SqlSource {
  2.         // 通过解析得到 BondSql 对象,BondSql 对象封装了包含“?”占位符的 SQL 语句,以及绑定的实参
  3.         BoundSql getBoundSql(Object parameterObject);
  4. }
复制代码


3.2.1 组合模式

组合模式是将对象组合成树形结构,以表现“部分——整体”的条理结构(一样平常是树形结构),用户可以像处置惩罚一个简单对象一样来处置惩罚一个复杂对象。


长处:
3.2.2 OGNL 表达式简介

MyBatis 中涉及的 OGNL 表达式的功能重要是:存取 Java 对象树中的属性、调用 Java 对象树中的方法
OGNL 表达式有三个重要概念:
示例代码:
MyBatis/MyBatis技能内幕/MyBatis-Tec-Inside/src/test/java/com/example/chapter3/section2/OgnlTest.java · cuizhigang/notes - 码云 - 开源中国 (gitee.com)
在 MyBatis 中,使用 OgnlCache 对原生的 OGNL 举行了封装。OGNL 表达式的剖析过程是比力耗时的,为了提高效率,OgnlCache 使用 expressionCache 字段(ConcurrentHashMap<String, Object>)对剖析后的 OGNL 表达式举行缓存。
  1. private static final Map<String, Object> expressionCache = new ConcurrentHashMap<>();
  2. public static Object getValue(String expression, Object root) {
  3.     try {
  4.         // 创建一个 OGNL 上下文对象,OgnlClassResolver 替代了 OGNL 中原有的 DefaultClassResolver
  5.         // 其主要功能是使用前面介绍的 Resource 工具类定位资源
  6.         Map<Object, OgnlClassResolver> context = Ognl.createDefaultContext(root, new OgnlClassResolver());
  7.         return Ognl.getValue(parseExpression(expression), context, root);
  8.     } catch (OgnlException e) {
  9.         throw new BuilderException("Error evaluating expression '" + expression + "'. Cause: " + e, e);
  10.     }
  11. }
  12. private static Object parseExpression(String expression) throws OgnlException {
  13.     // 从缓存中获取 OGNL 表达式对应的节点对象
  14.     Object node = expressionCache.get(expression);
  15.     if (node == null) {
  16.         // 如果缓存中不存在,则解析 OGNL 表达式
  17.         node = Ognl.parseExpression(expression);
  18.         // 将解析结果放入缓存
  19.         expressionCache.put(expression, node);
  20.     }
  21.     return node;
  22. }
复制代码
3.2.3 DynamicContext

DynamicContext 是用于记载剖析动态 SQL 语句之后产生的 SQL 语句片断的容器。
  1. public class DynamicContext {
  2.     // 参数上下文
  3.     private final ContextMap bindings;
  4.     // 在 SqlNode 解析动态 SQL 时,会将解析后的 SQL 语句片段添加到该属性中保存,最终拼凑出一条完整的 SQL
  5.     private final StringJoiner sqlBuilder = new StringJoiner(" ");
  6.    
  7.     // 追加 SQL 片段
  8.     public void appendSql(String sql) {
  9.         this.sqlBuilder.add(sql);
  10.     }
  11.     // 获取解析后的、完整的 SQL 语句
  12.     public String getSql() {
  13.         return this.sqlBuilder.toString().trim();
  14.     }
  15. }
复制代码
3.2.4 SqlNode

  1. public interface SqlNode {
  2.     // 根据用户传入的实参,解析该 SqlNode 所记录的动态 SQL 节点,并调用 DynamicContext.appendSql() 方法将解析后的 SQL 片段追加到 DynamicContext.sqlBuilder 中保存
  3.     // 当 SQL 节点下所有 SqlNode 完成解析后,我们就可以从 DynamicContext 中获取一条动态生成的、完整的 SQL 语句
  4.     boolean apply(DynamicContext context);
  5. }
复制代码
SqlNode 接口有多个实现类,每个实现类对应一个动态 SQL 节点。

StaticTextSqlNode&MixedSqlNode
StaticTextSqlNode 中使用 text 字段(String范例)记载了对应的非动态 SQL 语句节点。
MixedSqlNode 中使用 contents 字段(List范例)记载其子节点对应的 SqlNode 对象集合。
TextSqlNode
TextSqlNode 表现的是包罗“${}”占位符的动态 SQL 节点。
IfSqlNode
IfSqlNode 对应的动态 SQL 节点是 节点。使用 OGNL 检测表达式是否建立,并根据结构决定是否执行 apply() 方法。
TrimSqlNode&WhereSqlNode&SetSqlNode
TrimSqlNode 对应节点,会根据子节点的剖析结果,添加或删除相应的前缀或后缀。
WhereSqlNode 是 TrimSqlNode 的子类,对应节点
SetSqlNode 是 TrimSqlNode 的子类,对应节点
ForEachSqlNode
ForEachSqlNode 对应节点。
ChooseSqlNode
ChooseSqlNode,对应节点。
VarDeclSqlNode
VarDeclSqlNode 表现动态 SQL 语句中的 节点,该节点可以从 OGNL 表达式中创建一个变量并将其记载到上下文中。
  1. <select id="getUserInfo" parameterType="com.example.User" resultType="com.example.UserInfo">
  2.   <bind name="name" value="'%' + name + '%'"/>
  3.   <bind name="age" value="age * 2"/>
  4.   SELECT * FROM user_info  WHERE 1=1
  5.     <if test="name != null and name != ''">
  6.         AND name LIKE #{name}
  7.     </if>
  8.     <if test="age != null">
  9.         AND age >= #{age}
  10.     </if>
  11. </select>
复制代码
3.2.5 SqlSourceBuilder

SqlSourceBuilder 重要完成了两方面的操作,一方面是剖析 SQL 语句中的“#{}”占位符中界说的属性,另一方面是将 SQL 语句中的“#{}”占位符更换成“?”占位符。
3.2.6 DynamicSqlSource

DynamicSqlSource 负责剖析动态 SQL 语句。
  1. @Override
  2. public BoundSql getBoundSql(Object parameterObject) {
  3.     // 创建 DynamicContext 对象,parameterObject 为传入的参数
  4.     DynamicContext context = new DynamicContext(configuration, parameterObject);
  5.     // 通过 rootSqlNode.apply 方法调用整个树形结构中全部 SqlNode.apply 方法,
  6.     // 每个 SqlNode.apply 方法会将解析后的 SQL 语句片段追加到 context 中,最终通过 context.getSql() 获取完整的 SQL 语句
  7.     rootSqlNode.apply(context);
  8.     // 创建 SqlSourceBuilder 对象,解析 SQL 语句中的 #{} 占位符,将 SQL 语句中的 #{} 占位符替换成 ? 占位符
  9.     SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
  10.     Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
  11.     SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
  12.     // 创建 BoundSql 对象,并将DynamicContext.bindings 中的参数添加到 BoundSql.additionalParameters 集合中
  13.     BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
  14.     context.getBindings().forEach(boundSql::setAdditionalParameter);
  15.     return boundSql;
  16. }
复制代码
3.2.7 RawSqlSource

RawSqlSource 负责处置惩罚静态 SQL 语句。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4