zoukankan      html  css  js  c++  java
  • Spring mybatis源码篇章-Mybatis的XML文件加载

    通过阅读源码对实现机制进行了解有利于陶冶情操,承接前文Spring mybatis源码篇章-Mybatis主文件加载

    前话

    前文主要讲解了Mybatis的主文件加载方式,本文则分析不使用主文件加载方式,即SqlSessionFactoryBean对象不配置configLocation属性而配置mapperLocations属性

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    	<property name="mapperLocations" value="classpath:com/du/wx/resources/mapper/*.xml" />
    </bean>
    

    其中每个mapper文件均类似如下样例

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.du.wx.mapper.joggle.ActivityBookingDao">
    	<insert id="addBookingInfo">
    		insert into booking_activity_infos(uid,activityid,isbooked)
    		values(#{uid},#{activityid},#{isbooked})
    	</insert>
    
    	<select id="queryCountBookingByAct" resultType="int">
    		select count(*) from booking_activity_infos
    		where
    		activityid=#{0}
    	</select>
    	
    	<select id="queryCountBookingByUser" resultType="int">
    		select count(*) from booking_activity_infos
    		where
    		uid=#{0}
    	</select>
    	
    	<select id="queryUsersByAct" resultType="BaseUser">
    		select a.uid,a.openid,a.nickname,a.phone,a.email,a.address
    		from booking_activity_infos b 
    		right join 
    		base_user a on(b.uid=a.uid)
    		where b.activityid=#{0} and b.isbooked=1
    	</select>
    	
    	<select id="queryActByUser" resultType="Activity">
    		select a.id,a.title,a.content,a.url,a.address,a.time,a.type,a.status,a.limit,a.people
    		from activity a
    		left join 
    		booking_activity_infos b
    		on(a.id=b.activityid)
    		where b.uid=#{0} and b.isbooked=1
    	</select>
    	
    	<select id="queryActBooking" resultType="ActivityBooking">
    		select * from booking_activity_infos
    		where uid=#{0} and activityid=#{1}
    	</select>
    	
    	<update id="updateBooking">
    		update booking_activity_infos
    		set
    		isbooked=#{2}
    		where uid=#{0} and activityid=#{1}
    	</update>
    </mapper>
    

    SqlSessionFactoryBean读取mapper配置文件集合

    代码片段如下

    	if (!isEmpty(this.mapperLocations)) {
    	  //具体的如何从string转为Resource[],属于spring的基础操作了,有兴趣的读者可自行查阅
          for (Resource mapperLocation : this.mapperLocations) {
            if (mapperLocation == null) {
              continue;
            }
    
            try {
             //对扫描包及其子包下的每个mapper配置文件进行解析
              XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                  configuration, mapperLocation.toString(), configuration.getSqlFragments());
              xmlMapperBuilder.parse();
            } catch (Exception e) {
              throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
            } finally {
              ErrorContext.instance().reset();
            }
    
            if (this.logger.isDebugEnabled()) {
              this.logger.debug("Parsed mapper file: '" + mapperLocation + "'");
            }
          }
        } else {
          if (this.logger.isDebugEnabled()) {
            this.logger.debug("Property 'mapperLocations' was not specified or no matching resources found");
          }
        }
    

    主要是遍历mapper集合,然后对每个XML文件都通过XMLMapperBuilder对象来进行解析。这在前文也有提及

    XMLMapperBuilder

    笔者直接去观察其parse()方法,代码如下

    public void parse() {
    	//对每个xml资源只加载一次
        if (!configuration.isResourceLoaded(resource)) {
          //解析xml配置,其中配置的根节点必须为mapper
          configurationElement(parser.evalNode("/mapper"));
          //已加载
          configuration.addLoadedResource(resource);
          //绑定mapper的工作区间
          bindMapperForNamespace();
        }
    	
    	// 弥补措施
        parsePendingResultMaps();
        parsePendingChacheRefs();
        parsePendingStatements();
      }
    

    紧接着观察XMLMapperBuilder#configurationElement()方法,其来解析mapper文件

      private void configurationElement(XNode context) {
        try {
    	  //表明mapper根节点的namespace属性是必须的,且不为空
          String namespace = context.getStringAttribute("namespace");
          if (namespace.equals("")) {
        	  throw new BuilderException("Mapper's namespace cannot be empty");
          }
    	  //设置工作区间
          builderAssistant.setCurrentNamespace(namespace);
    	  //解析相应的属性
          cacheRefElement(context.evalNode("cache-ref"));
          cacheElement(context.evalNode("cache"));
    	  //解析<parameterMap>节点集合
          parameterMapElement(context.evalNodes("/mapper/parameterMap"));
    	  //解析<resultMap>节点集合
          resultMapElements(context.evalNodes("/mapper/resultMap"));
    	  //解析<sql>节点集合
          sqlElement(context.evalNodes("/mapper/sql"));
    	  //创建MappedStatement,这里与注解方式的加载方式还是类似的  
          buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
        } catch (Exception e) {
          throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
        }
      }
    

    1.mapper文件的namespace属性不可或缺,否则会抛异常

    2.mapper文件的parameterMap/resultMap/sql节点一般都是用来充当模板使用的

    3.mapper文件对应的数据库的增删改查节点分别为insert|delete|update|select

    笔者稍微对上述的节点作下简单的浏览

    resultMap

    解析mapper节点下的resultMap节点,相关代码如下

      private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
        ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
    	//读取id属性,最好配置以免不必要的错误
        String id = resultMapNode.getStringAttribute("id",
        resultMapNode.getValueBasedIdentifier());
        //优先级为type>ofType>resultType>javaType
        String type = resultMapNode.getStringAttribute("type",
        resultMapNode.getStringAttribute("ofType",
        resultMapNode.getStringAttribute("resultType",
        resultMapNode.getStringAttribute("javaType"))));
        
        String extend = resultMapNode.getStringAttribute("extends");
        //是否开启自动映射,默认值为unset
        Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
        Class<?> typeClass = resolveClass(type);
        
        //<discriminator><case /><case/></discriminator>根据结果值进行结果类型的映射,类似java的switch-case语法 
        Discriminator discriminator = null;
        //ResultMap节点信息转化为ResultMapping集合
        List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
        resultMappings.addAll(additionalResultMappings);
        List<XNode> resultChildren = resultMapNode.getChildren();
        for (XNode resultChild : resultChildren) {
            if ("constructor".equals(resultChild.getName())) {
    			//<resultMap>节点下<constructor>节点处理
    			processConstructorElement(resultChild, typeClass, resultMappings);
            } else if ("discriminator".equals(resultChild.getName())) {
                //<resultMap>节点下<discriminator>节点处理
                discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
            } else {
                //<id>/<result>/<collection>/<association>节点的解析
                ArrayList<ResultFlag> flags = new ArrayList<ResultFlag>();
                if ("id".equals(resultChild.getName())) {
                  flags.add(ResultFlag.ID);
                }
                resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
            }
        }
        ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
        try {
            //组装成ResultMap对象保存到Configuration对象的私有集合变量resultMaps
            return resultMapResolver.resolve();
        } catch (IncompleteElementException  e) {
            configuration.addIncompleteResultMap(resultMapResolver);
            throw e;
        }
      }
    

    具体的属性就不展开了,查阅相关的官方文档即可

    sql

    解析mapper节点下的sql节点。主要作用是将每个sql节点对象都保存到Configuration对象中的sqlFragments属性中(HashMap)。

      private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception {
        for (XNode context : list) {
    	  //sql节点的databaseId属性
          String databaseId = context.getStringAttribute("databaseId");
    	  //sql节点的id属性,id=${namespace}+"."+id
          String id = context.getStringAttribute("id");
          id = builderAssistant.applyCurrentNamespace(id, false);
    	  //true的前提是主配置文件指定了databaseId属性或者主配置和sql节点的databaseId属性均不存在,但sql节点的id属性存在
          if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) sqlFragments.put(id, context);
        }
      }  
    

    sql节点在配置id属性时,可简单的配置别名而不用带上namespace前缀,代码会自动校验进行拼装~~

    CRUD节点解析

    即解析select/update/delete/insert节点对应的信息,笔者此处关注XMLStatementBuilder#parseStatementNode()方法的片段代码

    	//节点上支持的常见属性
    	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");
        LanguageDriver langDriver = getLanguageDriver(lang);
        ...
    	String resultSetType = context.getStringAttribute("resultSetType");
    	StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    	...
    	// Include Fragments before parsing 导入<include>标签内容,其内部可以含有<if>/<where>/<set>等标签
        XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
        includeParser.applyIncludes(context.getNode());
    
    
        // Parse selectKey after includes and remove them.导入<selectKey>标签内容
        processSelectKeyNodes(id, parameterTypeClass, langDriver);
        
        // Parse the SQL (pre: <selectKey> and <include> were parsed and removed) 如何解析sql语句?放置下一章节讲解
        SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    
        ...
        //创建MappedStatement对象,保存在Configuration的mappedStatement集合属性中
        builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
    

    代码其实很长,不过跟前文的注解方式的解析是类似的,最终都是生成MappedStatement对象。
    而其会保存在org.apache.ibatis.session.Configuration对象中~~~~

    总结

    1.不管是通过注解模式还是XML文件模式,都会生成MappedStatement对象保存到Configuration对象中

    2.每个select|update|insert|delete标签均会被解析为单个MappedStatement对象,其中唯一ID为${namespace}.${id}

    3.Spring官方以及笔者都建议通过此方式来加载指定的XML文件,但是细心的笔者发现这其实并没有与DAO接口关联起来,看来还是得借助org.mybatis.spring.mapper.MapperScannerConfigurer的力量

  • 相关阅读:
    现代人的防范心理是不是太重
    几个概念
    Oracle 10G EM 的创建
    我爸和在我们小区里的一位老大爷
    Linux下Oracle10G服务的配置
    linux下使用文件来模拟硬盘进行ASM测试开发技术
    教你一步一步创建/配置Oracle9i Data Guard Manager
    “深入浅出”是什么导致不能以操作系统用户身份连接到数据库
    WCF 中,出现The remote server returned an unexpected response: (400) Bad Request.
    【题解】 「联合省选2020」信号传递 状压dp LOJ3302
  • 原文地址:https://www.cnblogs.com/question-sky/p/6612604.html
Copyright © 2011-2022 走看看