mybatis源码-映射文件解析过程

Mybatis底层源码分析(最详细的版本) Mybatis的底层源码深层剖析 阅读详情

本文github地址: 点击

本文gitee 地址: 点击

1. 前言

本章主要分析一下映射文件的解析过程,映射文件指的是mappper.xml 文件

一般我们会在映射文件中配置SQL语句、字段映射关系等等。映射文件中包含<cache>、<resultMap>、<sql> 等等二级节点。

下面开始分析吧!

2. 找准目标(映射文件解析的入口在哪?)

映射文件的解析过程是配置文件的解析过程的一部分,MyBatis会在解析配置文件的过程中对映射文件进行解析,具体解析逻辑在mapperElement方法内。

public class XMLConfigBuilder extends BaseBuilder {
  private void parseConfiguration(XNode root) {
    .....
    // 解析mappers 配置
    mapperElement(root.evalNode("mappers"));  
  }
   /**
   * 解析mapper映射文件
   * @param parent
   * @throws Exception
   */
  private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        // 获取 <package> 节点的name属性
        if ("package".equals(child.getName())) {
          /*
          <mappers>
            <package name="xxx.xxx.xxx"/>
          </mapper>
           */
          // 包名配置
          String mapperPackage = child.getStringAttribute("name");
          // 从指定包中查找 mapper接口,并根据mapper接口解析映射配置
          configuration.addMappers(mapperPackage);
        } else {
          // 获取 resource/url/class 等属性

          /*
          <mappers>
            <mapper resource="org/mybatis/mappers/UserMapper.xml"/>
          </mappers>
           */
          // 相对路径
          String resource = child.getStringAttribute("resource");
          /*
          <mappers>
            <mapper url="file:///var/mappers/UserMapper.xml"/>
          </mappers>
           */
          // 绝对路径
          String url = child.getStringAttribute("url");
          /*
          <mappers>
            <mapper class="org.mybatis.mappers.UserMapper"/>
          </mappers>
           */
          //接口信息配置
          String mapperClass = child.getStringAttribute("class");
          // 如果 resource 不为空 其他两者为空 则从相对路径中加载配置
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            // 解析配置文件
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
            // 绝对路径不为空时 加载绝对路径的文件
            ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            // 解析配置文件
            mapperParser.parse();
          } else if (resource == null && url == null && mapperClass != null) {
            // class配置
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            // 添加mapper
            configuration.addMapper(mapperInterface);
          } else {
            // 异常
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }
}

mapperElement 的主要逻辑就是遍历mappers节点中的mapper节点,根据节点值判断该如何加载映射文件信息。

  • resource 相对路径进行加载
  • url 绝对路径 加载问价
  • mapper接口class 加载
  • package 扫描包下的所有类

接下来我们看一下 映射文件解析入口parse方法

public class XMLMapperBuilder extends BaseBuilder {
  。。。。。
    public void parse() {
    // 先判断映射文件是否已经被加载过了
    if (!configuration.isResourceLoaded(resource)) {
      // 解析mapper节点
      configurationElement(parser.evalNode("/mapper"));
      // 添加资源路径到已解析资源集合中
      configuration.addLoadedResource(resource);
      // 通过命名空间绑定mapper接口
      bindMapperForNamespace();
    }
    // 处理未完成解析的节点
    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }
}
    

大致三个步骤:

  1. 解析mapper节点
  2. 通过命名空间绑定mapper接口
  3. 处理未完成解析的节点
2.1. 解析映射文件

映射文件包含很多种节点,这里就不举例了,上个简单mapper示例:

<mapper namespace="xxxx.xxx">
  <cache/>
  <resultMap id="BaseResultMap" type="xxx.xxx">
   <id column="id" jdbcType="BIGINT" property="id"/>
   <result column="name" jdbcType="VARCHAR" property="name"/>
   <result column="age" jdbcType="INT" property="age"/>
  </resultMap>
  <sql id="table"> user </sql>
  <select id="selectById" resultMap="BaseResultMap">
  	SELECT id, name, age FROM <includ refid="table"/>
    WHERE id = #{id}
  </select>
  
  ......
</mapper>

mapper文件的节点解析逻辑都有封装响应方法,然后由XMLMapperBuilder的configurationElement方法统一调用

public class XMLMapperBuilder extends BaseBuilder {
  /**
   * 统一调用节点解析配置
   */
  private void configurationElement(XNode context) {
    try {
      // 获取mapper namespace 命名空间
      // <mapper namespace="xxx.xxxx.xxxxMapper">
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.isEmpty()) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      // 设置namespace到builderAssistant 中
      builderAssistant.setCurrentNamespace(namespace);
      // 解析 <cache-ref> 节点
      cacheRefElement(context.evalNode("cache-ref"));
      // 解析 <cache> 节点
      cacheElement(context.evalNode("cache"));
      // 废弃节点 不做分析
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      // 解析 <resultMap> 节点
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      // 解析 <sql> 节点
      sqlElement(context.evalNodes("/mapper/sql"));
      // 解析 <select> <insert> <update> <delete>  等节点
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
  }
}
2.1.1. 解析 <cache> 节点

Mybatis 提供一二级缓存,一级缓存为SqlSession级别,默认开启,二级缓存配置在映射文件中,必须要显示配置才可以启用。如果没有特殊配置可以直接

<cache/>

比较复杂的:

<cache eviction="FIFO" flushInterval="60000" size="1024" readOnly="true"/>
  • eviction: 缓存策略选择先进先出
  • size:缓存容量为1024个对象引用
  • flushInterval:缓存每隔60秒刷新一次
  • readOnly 写安全,外部修改对象不会影响到缓存内部存储对象

当然还有其他缓存配置了,比如和Ehcache缓存整合,或者自己实现缓存等,这里不细究了。

缓存解析

public class XMLMapperBuilder extends BaseBuilder {
  /**
   * 缓存解析逻辑
   * @param context
   */
  private void cacheElement(XNode context) {
    if (context != null) {
      // 获取<cache>节点的各种属性
      String type = context.getStringAttribute("type", "PERPETUAL");
      Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
      String eviction = context.getStringAttribute("eviction", "LRU");
      Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
      Long flushInterval = context.getLongAttribute("flushInterval");
      Integer size = context.getIntAttribute("size");
      boolean readWrite = !context.getBooleanAttribute("readOnly", false);
      boolean blocking = context.getBooleanAttribute("blocking", false);
      // 子节点配置
      Properties props = context.getChildrenAsProperties();
      // 构建缓存对象
      builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
    }
  }
}

大致逻辑就是解析属性,然后构建缓存对象,构建方法为 MapperBuilderAssistant.userNewCache

我们来看看 MapperBuilderAssistant.userNewCache 方法吧

public class MapperBuilderAssistant extends BaseBuilder {
  /**
   * 构造缓存对象
   */
	public Cache useNewCache(Class<? extends Cache> typeClass,
      Class<? extends Cache> evictionClass,
      Long flushInterval,
      Integer size,
      boolean readWrite,
      boolean blocking,
      Properties props) {
    Cache cache = new CacheBuilder(currentNamespace)
        .implementation(valueOrDefault(typeClass, PerpetualCache.class))
        .addDecorator(valueOrDefault(evictionClass, LruCache.class))
        .clearInterval(flushInterval)
        .size(size)
        .readWrite(readWrite)
        .blocking(blocking)
        .properties(props)
        .build();
    // 添加缓存到 Configuration 对象中
    configuration.addCache(cache);
    // 设置 currentCache 遍历,即当前使用的缓存
    currentCache = cache;
    return cache;
  }
}

CacheBuilder.build 负责构建缓存

public class CacheBuilder {
  public Cache build() {
    // 设置默认的缓存类型(PerpetualCache)和缓存装饰器(LruCache)
    setDefaultImplementations();
    // 通过反射创建缓存
    Cache cache = newBaseCacheInstance(implementation, id);
    // 仅对内置缓存 PerpetualCache 应用装饰器
    setCacheProperties(cache);
    // issue #352, do not apply decorators to custom caches
    if (PerpetualCache.class.equals(cache.getClass())) {
      // 遍历装饰器集合,应用装饰器
      for (Class<? extends Cache> decorator : decorators) {
        // 通过反射创建装饰器实例
        cache = newCacheDecoratorInstance(decorator, cache);
        // 设置属性值到缓存实例中
        setCacheProperties(cache);
      }
      // 应用标准的装饰器,比如 LoggingCache、SynchronizedCache
      cache = setStandardDecorators(cache);
    } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
      // 应用具有日志功能的缓存装饰器
      cache = new LoggingCache(cache);
    }
    return cache;
  }

  private void setDefaultImplementations() {
    if (implementation == null) {
      // 设置默认的缓存实现类
      implementation = PerpetualCache.class;
      if (decorators.isEmpty()) {
        // 添加LrcCache装饰器
        decorators.add(LruCache.class);
      }
    }
  }
  private void setCacheProperties(Cache cache) {
    if (properties != null) {
      // 为缓存实例生成一个“元信息”实例,forObject 方法调用层次比较深,
      // 但最终调用了 MetaClass 的 forClass 方法。
      MetaObject metaCache = SystemMetaObject.forObject(cache);
      for (Map.Entry<Object, Object> entry : properties.entrySet()) {
        String name = (String) entry.getKey();
        String value = (String) entry.getValue();
        if (metaCache.hasSetter(name)) {
          // 获取 setter 方法的参数类型
          Class<?> type = metaCache.getSetterType(name);

          // 根据参数类型对属性值进行转换,并将转换后的值
          // 通过 setter 方法设置到 Cache 实例
          if (String.class == type) {
            metaCache.setValue(name, value);
          } else if (int.class == type
              || Integer.class == type) {
            metaCache.setValue(name, Integer.valueOf(value));
          } else if (long.class == type
              || Long.class == type) {
            metaCache.setValue(name, Long.valueOf(value));
          } else if (short.class == type
              || Short.class == type) {
            metaCache.setValue(name, Short.valueOf(value));
          } else if (byte.class == type
              || Byte.class == type) {
            metaCache.setValue(name, Byte.valueOf(value));
          } else if (float.class == type
              || Float.class == type) {
            metaCache.setValue(name, Float.valueOf(value));
          } else if (boolean.class == type
              || Boolean.class == type) {
            metaCache.setValue(name, Boolean.valueOf(value));
          } else if (double.class == type
              || Double.class == type) {
            metaCache.setValue(name, Double.valueOf(value));
          } else {
            throw new CacheException("Unsupported property type for cache: '" + name + "' of type " + type);
          }
        }
      }
    }
    // 如果缓存类实现了 InitializingObject 接口,
    // 则调用 initialize 方法执行初始化逻辑
    if (InitializingObject.class.isAssignableFrom(cache.getClass())) {
      try {
        ((InitializingObject) cache).initialize();
      } catch (Exception e) {
        throw new CacheException("Failed cache initialization for '"
          + cache.getId() + "' on '" + cache.getClass().getName() + "'", e);
      }
    }
  }
    private Cache setStandardDecorators(Cache cache) {
    try {
      // 创建“元信息”对象
      MetaObject metaCache = SystemMetaObject.forObject(cache);
      if (size != null && metaCache.hasSetter("size")) {
        // 设置 size 属性,
        metaCache.setValue("size", size);
      }
      if (clearInterval != null) {
        // clearInterval 不为空,应用 ScheduledCache 装饰器
        cache = new ScheduledCache(cache);
        ((ScheduledCache) cache).setClearInterval(clearInterval);
      }
      if (readWrite) {
        // readWrite 为 true,应用 SerializedCache 装饰器
        cache = new SerializedCache(cache);
      }
      /*
       * 应用 LoggingCache,SynchronizedCache 装饰器, * 使原缓存具备打印日志和线程同步的能力
       */
      cache = new LoggingCache(cache);
      cache = new SynchronizedCache(cache);
      if (blocking) {

        // blocking 为 true,应用 BlockingCache 装饰器
        cache = new BlockingCache(cache);
      }
      return cache;
    } catch (Exception e) {
      throw new CacheException("Error building standard cache decorators.  Cause: " + e, e);
    }
  }
}
  1. 设置默认的缓存类型及装饰器
  2. 应用装饰器到 PerpetualCache 对象上
  3. 应用标准装饰器
  4. 对非 LoggingCache 类型的缓存应用 LoggingCache 装饰器
2.1.2. 解析<cache-ref> 节点

Mybatis 二级缓存可以共用,通过 <cache-ref> 来配置参照缓存

大致逻辑已备注在代码中了 不做详细介绍了

public class XMLMapperBuilder extends BaseBuilder {
  private void cacheRefElement(XNode context) {
    if (context != null) {
      configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
      // cacheRefResolver示例
      CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
      try {
        // 解析参照缓存
        cacheRefResolver.resolveCacheRef();
      } catch (IncompleteElementException e) {
        // 捕捉IncompleteElementException 异常,并将 cacheRefResolver // 存入到 Configuration 的 incompleteCacheRefs 集合中
        configuration.addIncompleteCacheRef(cacheRefResolver);
      }
    }
  }
}
public class CacheRefResolver {
  private final MapperBuilderAssistant assistant;
  private final String cacheRefNamespace;

  public CacheRefResolver(MapperBuilderAssistant assistant, String cacheRefNamespace) {
    this.assistant = assistant;
    this.cacheRefNamespace = cacheRefNamespace;
  }

  public Cache resolveCacheRef() {
    // 调用 builderAssistant 的 useNewCache(namespace) 方法
    return assistant.useCacheRef(cacheRefNamespace);
  }
}

public class MapperBuilderAssistant extends BaseBuilder {
    public Cache useCacheRef(String namespace) {
    if (namespace == null) {
      throw new BuilderException("cache-ref element requires a namespace attribute.");
    }
    try {
      unresolvedCacheRef = true;
      // 根据命名空间从全局配置对象(Configuration)中查找相应的缓存实例
      Cache cache = configuration.getCache(namespace);
      /*
        若未查找到缓存实例,此处抛出异常。这里存在两种情况导致未查找到 cache实例,分别如下:
        1.使用者在 <cache-ref> 中配置了一个不存在的命名空间, 导致无法找到 cache 实例
        2.使用者所引用的缓存实例还未创建
       */
      if (cache == null) {
        throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.");
      }
      // 设置 cache 为当前使用缓存
      currentCache = cache;
      unresolvedCacheRef = false;
      return cache;
    } catch (IllegalArgumentException e) {
      throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.", e);
    }
  }
}
2.1.3. <resultMap>节点
 public class XMLMapperBuilder extends BaseBuilder {
   private void resultMapElements(List<XNode> list) {
    // 遍历 <resultMap>节点列表
    for (XNode resultMapNode : list) {
      try {
        // 解析resultMap节点
        resultMapElement(resultMapNode);
      } catch (IncompleteElementException e) {
        // ignore, it will be retried
      }
    }
  }
  private ResultMap resultMapElement(XNode resultMapNode) {
    return resultMapElement(resultMapNode, Collections.emptyList(), null);
  }

  private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) {
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());

    // 获取type属性
    String type = resultMapNode.getStringAttribute("type",
      resultMapNode.getStringAttribute("ofType",
        resultMapNode.getStringAttribute("resultType",
          resultMapNode.getStringAttribute("javaType"))));
    // 解析type属性对应的class类型
    Class<?> typeClass = resolveClass(type);
    if (typeClass == null) {
      typeClass = inheritEnclosingType(resultMapNode, enclosingType);
    }
    Discriminator discriminator = null;
    List<ResultMapping> resultMappings = new ArrayList<>(additionalResultMappings);
    // 获取并遍历 <resultMap> 的子节点列表
    List<XNode> resultChildren = resultMapNode.getChildren();
    for (XNode resultChild : resultChildren) {
      if ("constructor".equals(resultChild.getName())) {
        // 解析 constructor 节点,并生成相应的 ResultMapping
        processConstructorElement(resultChild, typeClass, resultMappings);
      } else if ("discriminator".equals(resultChild.getName())) {
        // 解析 discriminator 节点
        discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
      } else {
        List<ResultFlag> flags = new ArrayList<>();
        if ("id".equals(resultChild.getName())) {
          // 添加 ID 到 flags 集合中
          flags.add(ResultFlag.ID);
        }
        // 解析 id 和 property 节点,并生成相应的 ResultMapping
        resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
      }
    }
    // 获取 id 属性
    String id = resultMapNode.getStringAttribute("id",
      resultMapNode.getValueBasedIdentifier());
    // 获取extends属性
    String extend = resultMapNode.getStringAttribute("extends");
    // 获取autoMapping属性
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
    try {
      // 根据前面获取到的信息构建 ResultMap 对象
      return resultMapResolver.resolve();
    } catch (IncompleteElementException e) {
      /*
       * 如果发生 IncompleteElementException 异常,
       * 这里将 resultMapResolver 添加到 incompleteResultMaps 集合中 
       * */
      
      configuration.addIncompleteResultMap(resultMapResolver);
      throw e;
    }
  }
 }
  1. 获取<resultMap>节点的各种属性
  2. 遍历<resultMap>的子节点,并根据子节点名称执行相应的解析逻辑
  3. 构建 ResultMap 对象
  4. 若构建过程中发生异常,则将 resultMapResolver 添加到incompleteResultMaps 集合中
2.1.4. 解析<sql> 节点

入口:

public class XMLMapperBuilder extends BaseBuilder {

    /**
   * 统一调用节点解析配置
   */
  private void configurationElement(XNode context) {
    try {
      // 解析 <sql> 节点
      sqlElement(context.evalNodes("/mapper/sql"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
  }

有兴趣的可以自己跟踪源码看一下

2.1.5. 解析SQL语句节点

<select> <insert> <update> <delete> 节点的解析

具体跟踪源码吧 内容较多以后可能会完善

2.2. Mapper接口绑定
  1. 获取命名空间 根据命名空间解析mapper类型
  2. 将type和mapperProxyFactory实例存入konwnMappers中
  3. 解析注解中得信息
mybatis-plus 团队新作 mybatis-mate 轻松搞定数据权限 一、主要功能 字典绑定 字段加密 数据脱敏 表结构动态维护 数据审计记录 数据范围(数据权限) 数据库分库分表、动态据源、读写分离、数据库健康检查自动切换。 二、使用 2.1 依赖导入 Spring Boot 引入自动依赖注解包 <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-mate-starter</artifactId> < 阅读详情

相关推荐

mybatis-plus团队新作:mybatis-mate 轻松搞定数据权限.zip

计算机技术、IT咨询、人工智能AI理论介绍,学习参考资料 计算机技术、IT咨询、人工智能AI理论介绍,学习参考资料 计算机技术、IT咨询、人工智能AI理论介绍,学习参考资料 计算机技术、IT咨询、人工智能AI理论介绍,学习参考资料 计算机技术、IT咨询、人工智能AI理论介绍,学习参考资料 计算机技术、IT咨询、人工智能AI理论介绍,学习参考资料 计算机技术、IT咨询、人工智能AI理论介绍,学习参考资料 计算机技术、IT咨询、人工智能AI理论介绍,学习参考资料 计算机技术、IT咨询、人工智能AI理论介绍,学习参考资料 计算机技术、IT咨询、人工智能AI理论介绍,学习参考资料 计算机技术、IT咨询、人工智能AI理论介绍,学习参考资料 计算机技术、IT咨询、人工智能AI理论介绍,学习参考资料 计算机技术、IT咨询、人工智能AI理论介绍,学习参考资料 计算机技术、IT咨询、人工智能AI理论介绍,学习参考资料 计算机技术、IT咨询、人工智能AI理论介绍,学习参考资料 计算机技术、IT咨询、人工智能AI理论介绍,学习参考资料

mybatis源码(一)

1、启动mybtais 项目结构: 1.1 maven依赖 <dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency>

m0_38066054的博客 1250

西工大noj100题python2024第三季21-30

【代码】西工大noj100题python2024第三季21-30。

weixin_70523540的博客 1574

Mybatis源码解析

1.mybatis作为SSM中的持久层,我们都知道他是对JDBC的封装,那么他底层是如何实现的呢(新人记录学习,可能分析的不是很到位 各位别喷,下面分析并不是接口绑定的情况) 先从解析配置文件说起 先从这个方法作为如果,调用重载方法;类加载器为空, 又调用了classLoaderWrapper.getResourceAsStream getClassLoaders里面又创建了新的类加载器: ...

Hello stupid的博客 1810

Mask2Former实战:用SwinTransformer+Deformable Attention搞定图像分割三大任务

本文实战解析了Mask2Former模型如何统一解决语义、实例和全景分割三大任务。通过集成Swin Transformer骨干网络与创新的Deformable Attention及Masked Attention机制,详细介绍了模型构建、训练策略及在COCO数据集上的性能表现,为开发者提供了高效的图像分割解决方案。

tech5的博客 245

DeepSeek研发的精算大模型「精卫」已实现98%精算任务覆盖,中国人寿采用后年节约3.2亿元技术采购成本,形成关键技术替代效应

根据现有资料,精算领域关键技术替代效应的行业标准定义尚未明确具体提及。然而,可以从相关文献中提取一些关键概念和方法来推导出可能的定义。​替代效应的基本定义​:替代效应是指一种商品价格变动引起另一种商品的相对价格变动,从而导致消费者在维持原有效用水平不变的条件下对商品需求量做出的调整。​技术替代效应​:在精算领域,技术替代效应可以理解为新技术或新方法对现有技术或方法的替代,从而影响精算模型、数据处理和决策过程。例如,机器学习在精算中的应用日益增长,改变了精算师的工作方式和模型构建方法。​。

百态·数智思维 | 聚焦大数据、量化与未来AI 1710

MyBatis 源码分析 - 映射文件解析过程

文章目录1.简介2.映射文件解析过程分析2.1 解析映射文件2.1.1 解析 `` 节点2.1.2 解析 `` 节点2.1.3 解析` ` 节点2.1.3.1 解析 `` 和` ` 节点2.1.3.2 解析` 1.简介 在上一篇文章中,我详细分析了 MyBatis 配置文件的解析过程。由于上一篇文章的篇幅比较大,加之映射文件解析过程也比较复杂的原因。所以我将映射文件解析过程的分析内容从上一篇文章中抽取出来,独立成文,于是就有了本篇文章。在本篇文章中,我将分析映射文件中出现的一些及节点,比如<cache

ThinkWon的博客 3921

arduino uno spi驱动12864

arduino uno spi驱动12864

yvee的博客 2060

mybatis源码分析 映射文件解析过程

简介 在上一篇文章中,我详细分析了 MyBatis 配置文件的解析过程。由于上一篇文章的篇幅比较大,加之映射文件解析过程也比较复杂的原因。所以我将映射文件解析过程的分析内容从上一篇文章中抽取出来,独立成文,于是就有了本篇文章。在本篇文章中,我将分析映射文件中出现的一些及节点,比如 <cache>,<cache-ref>,<resultMap>, <select | insert | update | delete> 等。除了分析常规的 XML 解...

今天吃了吗的博客 355

逆向解析喜马拉雅新版xm-sign算法:从dws.1.6.8.js到签名生成

本文详细解析了喜马拉雅新版xm-sign算法的逆向破解方法。通过定位核心JS文件dws.1.6.8.js,构建本地Node.js模拟执行环境,并解密关键参数cid与appKey,最终实现签名算法的本地化稳定生成,为数据采集提供可靠解决方案。

weixin_29250403的博客 210

解读 2025《高质量数据集 质量评测规范》

《高质量数据集质量评测规范》为2025年数据质量评估提供标准化框架,适用于政府部门、AI企业、数据服务商等行业主体。规范以"说明文档-数据质量-模型应用"三维体系为核心:明确定义高质量数据集分类,细化各项指标要求(包括文档完整性、数据规范性、应用多样性等),并提供具体评分细则(百分制,90分达标)和计算方法。附录补充多模态数据处理细则,旨在建立可操作的质量评估标准,保障优质数据供给流通。

cdfunlove的博客 1878

Mybatis源码分析四之Mapper映射文件解析过程

一、Mapper.xml解析 前文分析了Configuration的解析过程,本文接着前文继续分析mapper映射文件的解析过程,前文可知映射文件的解析过程是由XMLMapperBuilder来完成的,所以我们可以跟着起parse方法一探究竟。 public void parse() { //判断是否已经加载过 if (!configuration.isResourceLoaded(resource)) { //解析映射文件 configurationElement(par

solayang的博客 999

STM32_HAL库—PWM产生指定个数

(一)CubeMX设置: 1. 晶振配置(72M) 2. 引脚配置 3. 中断配置 4. 配置及实现方式 由上推出公式: 机器周期: (72MHz / 36) = 2MHz = 0.5us PWM周期: 0.5 * 100 = 50us = 20kHz 5. 主函数调用PWM函数 HAL_TIM_PWM_Start_IT(&htim3, TIM_CHANNEL_2); //此处一句后可直接进入中断 6. PWM溢出回调函数 void HAL_TIM_PWM_PulseFinished

5525

Mybatis源码学习(22)-Mapper映射文件中个元素的解析过程

一、Mapper映射文件结构   根据定义Mapper映射文件结构的mybatis-3-mapper.dtd文件,可以知道Mapper映射文件的直接子元素有: cache – 对给定命名空间的缓存配置。 cache-ref – 对其他命名空间缓存配置的引用。 resultMap – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。 parameterMap – 已被废弃!老式风...

向心行者 1263

STM32入门教程:智能汽车控制

智能汽车是近年来快速发展的领域之一,它结合了嵌入式系统、传感器和通信技术,通过智能化的控制算法和决策系统实现自主导航、避障、环境感知等功能。在这篇教程中,我们将以 STM32 微控制器为核心,通过编写代码案例来介绍智能汽车的控制原理和实现方法。步骤三:决策与执行 最后,我们根据传感器数据做出相应的决策,并执行相应的动作。案例:智能汽车的避障功能 在这个案例中,我们将实现智能汽车的避障功能。感知系统负责感知汽车周围的环境,包括障碍物的距离、车辆的角度等信息。下面我们通过一个具体的案例来演示代码的编写。

2401_85258012的博客 926

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

MyBatis 学习笔记(六)---源码分析篇---映射文件的解析过程(一)

概述 前面几篇我们介绍了MyBatis中配置文件的解析过程。今天我们接着来看看MyBatis的另外一个核心知识点—映射文件的解析。本文将重点介绍<cache>节点和<cache-ref>的解析。 前置说明 Mapper 映射文件的解析是从XMLConfigBuilder类的对mappers 节点解析开始。mappers节点的配置有很多形式,如下图所示: <!-- 映...

码农飞哥 1775

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

mybatis源码学习之执行过程分析(2)——config.xml配置文件和mapper.xml映射文件解析过程

在上一篇中跟踪了SqlSessionFactory及SqlSession的创建过程。这一篇,主要跟踪Mapper接口和XML文件映射及获取。1.Mapper文件的解析1.1Mybatis-config.xml的解析在SqlSessionFactoryBuilder中执行build()方法时,其实做了配置文件的加载和解析,以及Configuration的初始化。SqlSessionFactoryBui

cai_cf 1697

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

【Mybatis源码分析】02-Mapper映射的解析过程

有两种情况,一种是TextSqlNode含有${..}一种是含有这种元素。到现在为止我们知道调用Mapper对象的方法实际就是调用apperMethodM对象的execute方法就可以了,后面分析完mapper的xml解析后再详细看一下MapperMethod,因为MapperMethod会用到解析mapper的xml文件解析出来的MappedStatement,毕竟调用Mapper接口的代理对象的方法才会出发execute方法,那时Mapper映射文件早已经解析好放入Configuration中了。

smart_an的专栏 1051

Mybatis源码阅读-配置文件及映射文件解析

Mybatis源码分析: 1.配置文件解析: 1.1源码阅读入口:org.apache.ibatis.builder.xml.XMLConfigBuilder.parse(); 功能:解析全局配置文件,调用parse.evalNode()方法,将指定路径的config配置文件转换为XNode对象,然后调用parseConfiguration()方法逐步解析配置文件...

weixin_30677475的博客 184

MyBatis源码分析(2)

全局配置文件可配置参数:https://mybatis.org/mybatis-3/zh/configuration.htmlConfiguration对象// 允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为false。默认为false protected boolean safeRowBoundsEnabled;// 允许在嵌套语句中使用分页(ResultHandler)。

u010156553的博客 289

Mybatis源码分析

title: Mybatis源码分析 date: 2021-02-17 11:21:28 tags: mybatis description: Mybatis源码学习 ​ (整体架构图) ​ .

Floyd的博客 173
上一篇: mybatis源码-配置文件解析过程
下一篇: IO多路复用
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值