栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Mybatis之Mapper.xml解析

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Mybatis之Mapper.xml解析

在springboot工程中,我们通过零配置的方式,配置了SqlSessionFactory

@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {

        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        //设置数据源
        sqlSessionFactoryBean.setDataSource(dataSource);
        //设置mybatis配置,这里设置了驼峰命名配置
        org.apache.ibatis.session.Configuration config = new org.apache.ibatis.session.Configuration();
        config.setMapUnderscoreToCamelCase(true);
        sqlSessionFactoryBean.setConfiguration(config);
        //设置Mapper.xml所在的位置
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
        return sqlSessionFactoryBean.getObject();
}

以上代码中除了设置了数据源,也设置了Mapper.xml所在的路径,然后通过sqlSessionFactoryBean.getObject()生成一个SqlSessionFactory对象交给spring管理,看getObject()方法:

@Override
public SqlSessionFactory getObject() throws Exception {
    if (this.sqlSessionFactory == null) {
      afterPropertiesSet();
    }

    return this.sqlSessionFactory;
}

因为sqlSessionFactoryBean是new出来的,没有交给spring管理,所以在这调用afterPropertiesSet方法,接下来会调到buildSqlSessionFactory方法:

protected SqlSessionFactory buildSqlSessionFactory() throws IOException {

    final Configuration targetConfiguration;

    .....
    .....
    //我们配置了mapper的路径,所以这里不为空

    if (!isEmpty(this.mapperLocations)) {
      for (Resource mapperLocation : this.mapperLocations) {
        if (mapperLocation == null) {
          continue;
        }

        try {
          XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
              targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
          xmlMapperBuilder.parse();
        } catch (Exception e) {
          throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
        } finally {
          ErrorContext.instance().reset();
        }
        LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
      }
    } else {
      LOGGER.debug(() -> "Property 'mapperLocations' was not specified or no matching resources found");
    }

    return this.sqlSessionFactoryBuilder.build(targetConfiguration);
  }

Mybatis中的类的命名可读性比较高,通过采用建造者模式来解析Mapper.xml,解析完成后放入Configuration对象中(在Mybatis中,所有的解析后的数据都会放入Configuration中,构建解析Mapper.xml的解析类是XMLMapperBuilder,解析Mybatis配置文件的是XMLConfigBuilder,解析增删改查sql的是XMLStatementBuilder等等,命名方便记忆)。

看XMLMapperBuilder.parse()方法,会对Mapper.xml中的各个标签做解析:

public void parse() {
    // 该节点是否被解析过
    if (!configuration.isResourceLoaded(resource)) {
      // 处理mapper节点
      configurationElement(parser.evalNode("/mapper"));
      // 加入已解析的列表,防止重复解析
      configuration.addLoadedResource(resource);
      // 将Mapper注册给configuration
      bindMapperForNamespace();
    }

    // 下面分别用来处理失败的、SQL语句
    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

private void configurationElement(XNode context) {
    try {
      // 读取当前映射文件namespace
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
      // 映射文件中其他配置节点的解析
      cacheRefElement(context.evalNode("cache-ref"));
      cacheElement(context.evalNode("cache"));
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      sqlElement(context.evalNodes("/mapper/sql"));
      // 顾名思义,处理增删改查
      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);
    }
  }

buildStatementFromContext方法中处理增删改查标签的解析:

public void parseStatementNode() {
    // 读取当前节点的id与databaseId
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");

    // 验证id与databaseId是否匹配。MyBatis允许多数据库配置,因此有些语句只对特定数据库生效
    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }

    // 读取节点名称
    String nodeName = context.getNode().getNodeName();
    // 读取和判断语句类型
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    //如果是select标签,则不刷新一级缓存,如果不是,则刷新一级缓存
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    // 处理语句中的Include节点
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    // 参数类型
    String parameterType = context.getStringAttribute("parameterType");
    Class parameterTypeClass = resolveClass(parameterType);

    // 语句类型
    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);

    // 处理SelectKey节点,在这里会将KeyGenerator加入到Configuration.keyGenerators中
    processSelectKeyNodes(id, parameterTypeClass, langDriver);

    // 此时, 节点均已被解析完毕并被删除,开始进行SQL解析
    KeyGenerator keyGenerator;
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    // 判断是否已经有解析好的KeyGenerator
    if (configuration.hasKeyGenerator(keyStatementId)) {
      keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
      // 全局或者本语句只要启用自动key生成,则使用key生成
      keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
          configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
          ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }

    // 读取各个配置属性
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    Integer fetchSize = context.getIntAttribute("fetchSize");
    Integer timeout = context.getIntAttribute("timeout");
    String parameterMap = context.getStringAttribute("parameterMap");
    String resultType = context.getStringAttribute("resultType");
    Class resultTypeClass = resolveClass(resultType);
    String resultMap = context.getStringAttribute("resultMap");
    String resultSetType = context.getStringAttribute("resultSetType");
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
    if (resultSetTypeEnum == null) {
      resultSetTypeEnum = configuration.getDefaultResultSetType();
    }
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    String resultSets = context.getStringAttribute("resultSets");

    // 在MapperBuilderAssistant的帮助下创建MappedStatement对象,并写入到Configuration中
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered,
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }

 这里会根据是否是select标签设置flushCache和useCache的默认值,可以看到一级和二级缓存在select标签中都是默认开启的。

sqlSource的解析:

public SqlSource parseScriptNode() {
    // 解析XML节点,得到节点树MixedSqlNode
    MixedSqlNode rootSqlNode = parseDynamicTags(context);
    SqlSource sqlSource;
    // 根据节点树是否为动态,创建对应的SqlSource对象
    if (isDynamic) {
      sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
      sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    return sqlSource;
 }

protected MixedSqlNode parseDynamicTags(XNode node) {
    // XNode拆分出的SqlNode列表
    List contents = new ArrayList<>();
    // 输入XNode的子XNode
    NodeList children = node.getNode().getChildNodes();
    for (int i = 0; i < children.getLength(); i++) {
      // 循环遍历每一个子XNode
      XNode child = node.newXNode(children.item(i));
      if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) { // CDATA类型或者text类型的XNode节点
        // 获取XNode内的信息
        String data = child.getStringBody("");
        TextSqlNode textSqlNode = new TextSqlNode(data);
        // 只要有一个TextSqlNode对象是动态的,则整个MixedSqlNode就是动态的
        if (textSqlNode.isDynamic()) {
          contents.add(textSqlNode);
          isDynamic = true;
        } else {
          contents.add(new StaticTextSqlNode(data));
        }
      } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628 // 子XNode类型任然是Node类型
        String nodeName = child.getNode().getNodeName();
        // 找到对应的处理器
        NodeHandler handler = nodeHandlerMap.get(nodeName);
        if (handler == null) {
          throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
        }
        // 用处理器处理节点
        handler.handleNode(child, contents);
        isDynamic = true;
      }
    }
    // 返回一个混合节点,其实就是一个SQL节点树
    return new MixedSqlNode(contents);
  }

看一个select标签的sqlSource解析,示例:

 

 通过上图可以看到一个树状的集合结构:

 这个树状的sqlNode组成了一个MixedSqlNode(也实现了SqlNode接口)。

因为select标签中存在这种动态标签,所以会返回一个DynamicSqlSource(对于 TextSqlNode对象而言,如果内 部含有“${}”占位符,那它就是动态的,否则就不是动态的):

 这样就根据解析到的增删改查(select|insert|update|delete)标签的参数和sqlSource,通过建造者模式创建好一个MappedStatement对象,放入Configuration中。

说明:每一个Mapper.xml中的增删改查的标签都对应一个MappedStatement对象,将Mapper.xml中的namespace+标签ID作为key,MappedStatement对象作为value,存入Configuration中的mappedStatements集合当中。

 

Mapper.xml中的节点处理完毕之后,接下来会将Mapper注册到Configuration

 

会根据namespace反射后加入Configuration:

 以后会根据这里的映射关系,根据接口生成一个jdk代理对象。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/865200.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号