第三章-Mybatis源码解析-以xml方式走流程-mapper解析(二)

3.2.2.1 cache-ref节点解析
private void cacheRefElement(XNode context) {
    if (context != null) {
       // 取出该节点的属性值namespace,并存到configuration中,内部是一个HashMap集合,看如下`关联1`
      configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
       // 创建 CacheRefResolver 对象,看如下`关联2`
      CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
      try {
        // 解析cache-ref后,从configuration中拿到namespace对应的cache,作为当前缓存使用,看如下`关联3`
        cacheRefResolver.resolveCacheRef();
      } catch (IncompleteElementException e) {
        // 解析失败,将其添加到未完成列表中,内部是LinkedList实现,就是章节`3.2.1`中Pending要清除的
        configuration.addIncompleteCacheRef(cacheRefResolver);
      }
    }
  }
// 关联1:存放cache-ref的配置值
public void addCacheRef(String namespace, String referencedNamespace) {
    cacheRefMap.put(namespace, referencedNamespace);
}
// 关联2:构造函数中就是普通赋值
public CacheRefResolver(MapperBuilderAssistant assistant, String cacheRefNamespace) {
    this.assistant = assistant;
    this.cacheRefNamespace = cacheRefNamespace;
}
// 关联3
public Cache resolveCacheRef() {
    return assistant.useCacheRef(cacheRefNamespace);
}
public Cache useCacheRef(String namespace) {
    if (namespace == null) { // 判空
        throw new BuilderException("cache-ref element requires a namespace attribute.");
    }
    try {
        unresolvedCacheRef = true;
        // 从 configuration中取出namespace对应的cache,内部也是HashMap集合存放
        Cache cache = configuration.getCache(namespace);
        if (cache == null) { // 再次判空
            throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.");
        }
        // 作为当前缓存对象
        currentCache = cache;
        unresolvedCacheRef = false;
        return cache;
    } catch (IllegalArgumentException e) {
        throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.", e);
    }
}
3.2.2.2 cache节点解析
private void cacheElement(XNode context) {
    if (context != null) {
        // type表示自定义cache实现类
        String type = context.getStringAttribute("type", "PERPETUAL");
        // 判断是不是别名,并解析
        Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
        // 缓存清除策略,默认LRU
        String eviction = context.getStringAttribute("eviction", "LRU");
        /* 判断是不是别名,并解析 eviction 策略对应的实现类,Mybatis默认4个实现方式FifoCache、LruCache、SoftCache、WeakCache,分别对应FIFO、LRU、SOFT、WEAK策略
         LRU – 最近最少使用:移除最长时间不被使用的对象。
		FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
		SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
		WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
        */
        Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
        // flushInterval(刷新间隔)
        Long flushInterval = context.getLongAttribute("flushInterval");
        // size(引用数目)属性可以被设置为任意正整数,要注意缓存对象的大小和运行环境中可用的内存资源。默认值是 1024
        Integer size = context.getIntAttribute("size");
        // readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。
        boolean readWrite = !context.getBooleanAttribute("readOnly", false);
        boolean blocking = context.getBooleanAttribute("blocking", false);
        // 解析cache节点下的子节点property,并全部解析为属性信息
        Properties props = context.getChildrenAsProperties();
        // 构建缓存对象Cache
        builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
    }
}

构建缓存对象

public Cache useNewCache(Class<? extends Cache> typeClass,
      Class<? extends Cache> evictionClass,
      Long flushInterval,
      Integer size,
      boolean readWrite,
      boolean blocking,
      Properties props) {
    // 通过命名空间作为id,来唯一标识缓存范围
    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(); // 真正构建Cache的地方,继续往下
    // 将创建的 cache 对象保存到 configuration 的 HashMap中,key 就是 命名空间,value 就是 cache
    configuration.addCache(cache);
    currentCache = cache; // 设置当前使用的缓存对象
    return cache; // 返回
  }

CacheBuilder构建Cache

public Cache build() {
    // 如何用户没有自定义Cache实现类,就用默认的PerpetualCache
    setDefaultImplementations();
    // 这里就是通过反射创建 implementation 对应的类的对象
    Cache cache = newBaseCacheInstance(implementation, id);
    // 检验cache的setter方法,并把对应的setter方法通过属性名称,将属性值设置进去,也就是赋值
    setCacheProperties(cache);
    // issue #352, do not apply decorators to custom caches
    // 下面这一段代码就是分别区分自定义缓存实现和默认缓存实现,并通过装饰器模式,强化缓存的实现,也就是说,不管是默认的缓存实现PerpetualCache,还是自定义的,都得在外再包一层或多层Mybatis实现的缓存
    if (PerpetualCache.class.equals(cache.getClass())) {
        for (Class<? extends Cache> decorator : decorators) {
            // Mybatis自带的用于装饰的缓存实现,构建函数都需要传入一个被包装的缓存实现,所以这里要传入cache并反射创建装饰的缓存实现
            cache = newCacheDecoratorInstance(decorator, cache);
            setCacheProperties(cache); // 设置属性
        }
        // Mybatis自身通过装饰器再包一层,具体就在这实现,看`关联1`
        cache = setStandardDecorators(cache);
    } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
        // 如果自定义的类不是LoggingCache了类,那得用 LoggingCache 装饰一层
        cache = new LoggingCache(cache); 
    }
    // 返回最终的cache
    return cache;
}

private void setDefaultImplementations() {
    if (implementation == null) {
        implementation = PerpetualCache.class;
        if (decorators.isEmpty()) {
            decorators.add(LruCache.class);
        }
    }
}

// 关联1
private Cache setStandardDecorators(Cache cache) {
    try {
        MetaObject metaCache = SystemMetaObject.forObject(cache);
        // 设置size
        if (size != null && metaCache.hasSetter("size")) {
            metaCache.setValue("size", size);
        }
        // 设置 clearInterval
        if (clearInterval != null) {
            cache = new ScheduledCache(cache);
            ((ScheduledCache) cache).setClearInterval(clearInterval);
        }
        // 可读写缓存,用SerializedCache装饰一层
        if (readWrite) {
            cache = new SerializedCache(cache);
        }
        // 再用LogginCache装饰一层
        cache = new LoggingCache(cache);
        // 再用 SynchronizedCache 装饰一层
        cache = new SynchronizedCache(cache);
        // blocking 为 true,再用 BlockingCache 装饰一层
        if (blocking) { 
            cache = new BlockingCache(cache);
        }
        // 返回最外层的cache
        return cache;
    } catch (Exception e) {
        throw new CacheException("Error building standard cache decorators.  Cause: " + e, e);
    }
}
3.2.2.3 resultMap节点解析

先看看 resultMap 节点中有什么,结果映射(resultMap)

  • constructor - 用于在实例化类时,注入结果到构造方法中
    • idArg - ID 参数;标记出作为 ID 的结果可以帮助提高整体性能
    • arg - 将被注入到构造方法的一个普通结果
  • id – 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能
  • result – 注入到字段或 JavaBean 属性的普通结果
  • association – 一个复杂类型的关联;许多结果将包装成这种类型
    • 嵌套结果映射 – 关联可以是 resultMap 元素,或是对其它结果映射的引用
  • collection – 一个复杂类型的集合
    • 嵌套结果映射 – 集合可以是 resultMap 元素,或是对其它结果映射的引用
  • discriminator – 使用结果值来决定使用哪个resultMap
    • case – 基于某些值的结果映射
      • 嵌套结果映射 – case 也是一个结果映射,因此具有相同的结构和元素;或者引用其它的结果映射

所以,下面的代码,就是把resultMap的配置解析成 ResultMap 对象,并存放到 configuration 的 HashMap 中,由于正常生产环境的 resultMap 配置不会很复杂,但是它定义的又很复杂,解析起来,又繁琐,又枯燥,并且意义不大,所以这段代码就不一一解析了,有兴趣的读者可以自行研究。

private void resultMapElements(List<XNode> list) throws Exception {
    for (XNode resultMapNode : list) { // 遍历子节点
        try {
            // 解析节点,继续往下看
            resultMapElement(resultMapNode);
        } catch (IncompleteElementException e) {
            // ignore, it will be retried
        }
    }
}

private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
    return resultMapElement(resultMapNode, Collections.emptyList(), null);
}

private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) throws Exception {
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
    String type = resultMapNode.getStringAttribute("type",
                                                   resultMapNode.getStringAttribute("ofType",
                                                                                    resultMapNode.getStringAttribute("resultType",
                                                                                                                     resultMapNode.getStringAttribute("javaType"))));
    Class<?> typeClass = resolveClass(type);
    if (typeClass == null) {
        typeClass = inheritEnclosingType(resultMapNode, enclosingType);
    }
    Discriminator discriminator = null;
    List<ResultMapping> resultMappings = new ArrayList<>();
    resultMappings.addAll(additionalResultMappings);
    List<XNode> resultChildren = resultMapNode.getChildren();
    for (XNode resultChild : resultChildren) {
        if ("constructor".equals(resultChild.getName())) {
            processConstructorElement(resultChild, typeClass, resultMappings);
        } else if ("discriminator".equals(resultChild.getName())) {
            discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
        } else {
            List<ResultFlag> flags = new ArrayList<>();
            if ("id".equals(resultChild.getName())) {
                flags.add(ResultFlag.ID);
            }
            resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
        }
    }
    String id = resultMapNode.getStringAttribute("id",
                                                 resultMapNode.getValueBasedIdentifier());
    String extend = resultMapNode.getStringAttribute("extends");
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
    try {
        return resultMapResolver.resolve();
    } catch (IncompleteElementException  e) {
        configuration.addIncompleteResultMap(resultMapResolver);
        throw e;
    }
}
3.2.2.4 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节点里的sql内容进行解析,而是直接把当前的sql节点XNode对象整个存放到 sqlFragments 这个Map中
            sqlFragments.put(id, context);
        }
    }
}

private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
    if (requiredDatabaseId != null) {
        return requiredDatabaseId.equals(databaseId);
    }
    if (databaseId != null) {
        return false;
    }
    if (!this.sqlFragments.containsKey(id)) {
        return true;
    }
    // skip this fragment if there is a previous one with a not null databaseId
    XNode context = this.sqlFragments.get(id);
    return context.getStringAttribute("databaseId") == null;
}
3.2.2.5 select|insert|update|delete解析

调用入口在 XMLMapperBuilder.java 类中

private void buildStatementFromContext(List<XNode> list) {
    if (configuration.getDatabaseId() != null) {
        buildStatementFromContext(list, configuration.getDatabaseId());
    }
    buildStatementFromContext(list, null);
}

private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
        final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
        try {
            // 真正解析在这里
            statementParser.parseStatementNode();
        } catch (IncompleteElementException e) {
            configuration.addIncompleteStatement(statementParser);
        }
    }
}

真正解析操作在 XMLStatementBuilder.java 类中

public void parseStatementNode() {
    String id = context.getStringAttribute("id"); // 语句的id
    String databaseId = context.getStringAttribute("databaseId");

    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
        return;
    }
    // 节点名,用于判断 select|insert|update|delete
    String nodeName = context.getNode().getNodeName();
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT; // 判断是否select
    // 非 select 时,flushCache 为 true,也就是说 insert|update|delete 语句的执行会刷新缓存
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    // select 时,useCache 默认为 true,将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对 select 元素为 true
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    // 这个设置仅针对嵌套结果 select 语句:如果为 true,将会假设包含了嵌套结果集或是分组,当返回一个主结果行时,就不会产生对前面结果集的引用。 这就使得在获取嵌套结果集的时候不至于内存不够用。默认值:false
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    // include 解析
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());
    // parameterType 将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)
    String parameterType = context.getStringAttribute("parameterType");
    Class<?> parameterTypeClass = resolveClass(parameterType);
    // 获取 LanguageDriver,默认实现 XMLLanguageDriver,后面会用它来解析sql并生成SqlSource对象,还有一个实现是 RawLanguageDriver,具体用到时再讲
    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);

    // Parse selectKey after includes and remove them.
    // selectKey 语句的解析,这个内容也比较多,专门抽出一节来讲,看章节`3.2.2.7`
    processSelectKeyNodes(id, parameterTypeClass, langDriver);

    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    KeyGenerator keyGenerator;
    //  SelectKeyGenerator.SELECT_KEY_SUFFIX = "!selectKey"
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    // 最终 keyStatementId = namespace + 语句id + "!selectKey"
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    // keyGenerator 在上面解析selectKey的方法 processSelectKeyNodes 中生成
    if (configuration.hasKeyGenerator(keyStatementId)) {
        // 有就取出来
        keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
        // 没有就根据 useGeneratedKeys是否为true及是否为insert语句来判断,使用 Jdbc3KeyGenerator,否则为 NoKeyGenerator
        keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
                                                   configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
            ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }
    // 生成 sqlSource ,细节看章节`3.2.2.6`
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    // 开始 ========== 下面这些值,官网都有介绍,我就再写一遍吧 ==============//
    // statementType	可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    // fetchSize	这是一个给驱动的建议值,尝试让驱动程序每次批量返回的结果行数等于这个设置值。 默认值为未设置(unset)(依赖驱动)
    Integer fetchSize = context.getIntAttribute("fetchSize");
    // timeout	这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。
    Integer timeout = context.getIntAttribute("timeout");
    // parameterMap	用于引用外部 parameterMap 的属性,目前已被废弃。请使用行内参数映射和 parameterType 属性。
    String parameterMap = context.getStringAttribute("parameterMap");
    // resultType	期望从这条语句中返回结果的类全限定名或别名。 注意,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。 resultType 和 resultMap 之间只能同时使用一个。
    String resultType = context.getStringAttribute("resultType");
    Class<?> resultTypeClass = resolveClass(resultType);
    // resultMap	对外部 resultMap 的命名引用。结果映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂的映射问题都能迎刃而解。 resultType 和 resultMap 之间只能同时使用一个。
    String resultMap = context.getStringAttribute("resultMap");
    // resultSetType	FORWARD_ONLY,SCROLL_SENSITIVE, SCROLL_INSENSITIVE 或 DEFAULT(等价于 unset) 中的一个,默认值为 unset (依赖数据库驱动)。
    String resultSetType = context.getStringAttribute("resultSetType");
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
    if (resultSetTypeEnum == null) {
        resultSetTypeEnum = configuration.getDefaultResultSetType();
    }
    // keyProperty	(仅适用于 insert 和 update)指定能够唯一识别对象的属性,MyBatis 会使用 getGeneratedKeys 的返回值或 insert 语句的 selectKey 子元素设置它的值,默认值:未设置(unset)。如果生成列不止一个,可以用逗号分隔多个属性名称。
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    String resultSets = context.getStringAttribute("resultSets");
    
     // ========== 下面这些值,官网都有介绍,我就再写一遍吧 ============== 结束//
    // 创建 MappedStatement 并存放到 configuration 的 HashMap 中,细节放到章节`3.2.2.6`中
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
                                        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
                                        resultSetTypeEnum, flushCache, useCache, resultOrdered,
                                        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

后续将通过抖音视频/直播的形式分享技术,由于前期要做一些准备和规划,预计2024年6月开始,欢迎关注,如有需要或问题咨询,也可直接抖音沟通交流。
在这里插入图片描述

  • 8
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

多栖码农

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值