• 深入Mybatis源码——配置解析


    @

    前言

    上一篇分析了Mybatis的基础组件,Mybatis的运行调用就是建立在这些基础组件之上的,那它的执行原理又是怎样的呢?在往下之前不妨先思考下如果是你会怎么实现。

    正文

    熟悉Mybatis的都知道,在使用Mybatis时需要配置一个mybatis-config.xml文件,另外还需要定义Mapper接口和Mapper.xml文件,在config文件中引入或扫描对应的包才能被加载解析(现在由于大多是SpringBoot工程,基本上都不会配置config文件,而是通过注解进行扫描就行了,但本质上的实现和xml配置没有太大区别,所以本篇仍以xml配置方式进行分析。),所以Mybatis的第一个阶段必然是要去加载并解析配置文件,这个阶段在项目启动时就应该完成,后面直接调用即可。加载完成之后,自然就是等待调用,但是我们在项目中只会定义Mapper接口和Mapper.xml文件,那具体的实现类在哪呢?Mybatis是通过动态代理实现的,所以第二个阶段应该是生成Mapper接口的代理实现类。通过调用代理类,最终会生成对应的sql访问数据库并获取结果,所以最后一个阶段就是SQL解析(参数映射、SQL映射、结果映射)。本文主要分析配置解析阶段。

    配置解析

    Mybatis可以通过下面的方式解析配置文件:

        final String resource = "org/apache/ibatis/builder/MapperConfig.xml";
        final Reader reader = Resources.getResourceAsReader(resource);
        SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
    

    所以入口就是build方法(从名字可以看出使用了建造者模式,它和工厂模式一样,也是解用于创建对象的一种模式,不过与工厂模式不一样的是,前者需要我们自己参与构建的细节,而后者则不需要):

      public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
        try {
          //读取配置文件
          XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
          return build(parser.parse());//解析配置文件得到configuration对象,并返回SqlSessionFactory
        } catch (Exception e) {
          throw ExceptionFactory.wrapException("Error building SqlSession.", e);
        } finally {
          ErrorContext.instance().reset();
          try {
            reader.close();
          } catch (IOException e) {
            // Intentionally ignore. Prefer previous error.
          }
        }
      }
    

    这里先是创建了一个XMLConfigBuilder对象,这个对象就是用来加载解析config文件的,先看看它的构造方法中做了些什么事情:

      public XMLConfigBuilder(Reader reader, String environment, Properties props) {
        this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
      }
    
      private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
        super(new Configuration());
        ErrorContext.instance().resource("SQL Mapper Configuration");
        this.configuration.setVariables(props);
        this.parsed = false;
        this.environment = environment;
        this.parser = parser;
      }
    

    需要注意的是这里创建了一个Configuration对象,他就是Mybatis的核心CPU,保存了所有的配置信息,在后面的执行阶段所需要的信息都是从这个类取的,因为这个类比较大,这里就不贴详细代码了,读者请务必阅读源码熟悉该类。因为这个类对象保存了所有的配置信息,那么必然这个类是全局单例的,事实上这个对象的创建也只有这里一个入口,保证了全局唯一。
    在该类的构造方法中,首先就注册了核心组件的别名和对应的类映射关系:

      public Configuration() {
        typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
        typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
    
        typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
        typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
        typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
    
    	......省略
    
        languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
        languageRegistry.register(RawLanguageDriver.class);
      }
    

    而注册类在实例化时同样也注册了一些基础类型的别名映射:

      public TypeAliasRegistry() {
        registerAlias("string", String.class);
    
        registerAlias("byte", Byte.class);
        registerAlias("long", Long.class);
        registerAlias("short", Short.class);
        registerAlias("int", Integer.class);
        registerAlias("integer", Integer.class);
        registerAlias("double", Double.class);
        registerAlias("float", Float.class);
        registerAlias("boolean", Boolean.class);
    
    	......省略
    	
        registerAlias("ResultSet", ResultSet.class);
      }
    

    看到这相信你就知道parameterType和resultType属性的简写是怎么实现的了。回到主流程,进入到parser.parse方法中:

      public Configuration parse() {
        if (parsed) {
          throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        parsed = true;
        parseConfiguration(parser.evalNode("/configuration"));
        return configuration;
      }
    
      private void parseConfiguration(XNode root) {
        try {
          //issue #117 read properties first
         //解析<properties>节点
          propertiesElement(root.evalNode("properties"));
          //解析<settings>节点
          Properties settings = settingsAsProperties(root.evalNode("settings"));
          loadCustomVfs(settings);
          //解析<typeAliases>节点
          typeAliasesElement(root.evalNode("typeAliases"));
          //解析<plugins>节点
          pluginElement(root.evalNode("plugins"));
          //解析<objectFactory>节点
          objectFactoryElement(root.evalNode("objectFactory"));
          //解析<objectWrapperFactory>节点
          objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
          //解析<reflectorFactory>节点
          reflectorFactoryElement(root.evalNode("reflectorFactory"));
          settingsElement(settings);//将settings填充到configuration
          // read it after objectFactory and objectWrapperFactory issue #631
          //解析<environments>节点
          environmentsElement(root.evalNode("environments"));
          //解析<databaseIdProvider>节点
          databaseIdProviderElement(root.evalNode("databaseIdProvider"));
          //解析<typeHandlers>节点
          typeHandlerElement(root.evalNode("typeHandlers"));
          //解析<mappers>节点
          mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
          throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
      }
    

    这个方法就是去解析配置文件中的各个节点,并将其封装到Configuration对象中去,前面的节点解析没啥好说的,自己看一下就明白了,重点看一下最后一个对mapper节点的解析,这个就是加载我们的Mapper.xml文件:

      <mappers>
        <mapper resource="org/apache/ibatis/builder/AuthorMapper.xml"/>
        <mapper resource="org/apache/ibatis/builder/BlogMapper.xml"/>
        <mapper resource="org/apache/ibatis/builder/CachedAuthorMapper.xml"/>
        <mapper resource="org/apache/ibatis/builder/PostMapper.xml"/>
        <mapper resource="org/apache/ibatis/builder/NestedBlogMapper.xml"/>
      </mappers>
    
     private void mapperElement(XNode parent) throws Exception {
        if (parent != null) {
          for (XNode child : parent.getChildren()) {//处理mapper子节点
            if ("package".equals(child.getName())) {//package子节点
              String mapperPackage = child.getStringAttribute("name");
              configuration.addMappers(mapperPackage);
            } else {//获取<mapper>节点的resource、url或mClass属性这三个属性互斥
              String resource = child.getStringAttribute("resource");
              String url = child.getStringAttribute("url");
              String mapperClass = child.getStringAttribute("class");
              if (resource != null && url == null && mapperClass == null) {//如果resource不为空
                ErrorContext.instance().resource(resource);
                InputStream inputStream = Resources.getResourceAsStream(resource);//加载mapper文件
                //实例化XMLMapperBuilder解析mapper映射文件
                XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
                mapperParser.parse();
              } else if (resource == null && url != null && mapperClass == null) {//如果url不为空
                ErrorContext.instance().resource(url);
                InputStream inputStream = Resources.getUrlAsStream(url);//加载mapper文件
                //实例化XMLMapperBuilder解析mapper映射文件
                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);//加载class对象
                configuration.addMapper(mapperInterface);//向代理中心注册mapper
              } else {
                throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
              }
            }
          }
        }
      }
    

    从上面的代码中我们可以看到这里有两种配置方式,一种是配置package子节点,即扫描并批量加载指定的包中的文件;另一种则是使用mapper子节点引入单个文件,而mapper节点又可以配置三种属性:resource、url、class。而解析XML的核心类是XMLMapperBuilder,进入parse方法:

      public void parse() {
    	//判断是否已经加载该配置文件
        if (!configuration.isResourceLoaded(resource)) {
          configurationElement(parser.evalNode("/mapper"));//处理mapper节点
          configuration.addLoadedResource(resource);//将mapper文件添加到configuration.loadedResources中
          bindMapperForNamespace();//注册mapper接口
        }
        //处理解析失败的ResultMap节点
        parsePendingResultMaps();
        //处理解析失败的CacheRef节点
        parsePendingCacheRefs();
        //处理解析失败的Sql语句节点
        parsePendingStatements();
      }
    
      private void configurationElement(XNode context) {
        try {
        	//获取mapper节点的namespace属性
          String namespace = context.getStringAttribute("namespace");
          if (namespace == null || namespace.equals("")) {
            throw new BuilderException("Mapper's namespace cannot be empty");
          }
          //设置builderAssistant的namespace属性
          builderAssistant.setCurrentNamespace(namespace);
          //解析cache-ref节点
          cacheRefElement(context.evalNode("cache-ref"));
          //重点分析 :解析cache节点----------------1-------------------
          cacheElement(context.evalNode("cache"));
          //解析parameterMap节点(已废弃)
          parameterMapElement(context.evalNodes("/mapper/parameterMap"));
          //重点分析 :解析resultMap节点(基于数据结果去理解)----------------2-------------------
          resultMapElements(context.evalNodes("/mapper/resultMap"));
          //解析sql节点
          sqlElement(context.evalNodes("/mapper/sql"));
          //重点分析 :解析select、insert、update、delete节点 ----------------3-------------------
          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);
        }
      }
    

    核心的处理逻辑又是通过configurationElement实现的,接下来挨个分析几个重要节点解析过程。

    1. cacheRefElement/cacheElement

    这两个节点都是解析二级缓存配置的,前者是引用其它namespace的二级缓存,后者则是直接开启当前namespace的二级缓存,所以重点看后者:

      private void cacheElement(XNode context) throws Exception {
        if (context != null) {
          //获取cache节点的type属性,默认为PERPETUAL
          String type = context.getStringAttribute("type", "PERPETUAL");
          //找到type对应的cache接口的实现
          Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
          //读取eviction属性,既缓存的淘汰策略,默认LRU
          String eviction = context.getStringAttribute("eviction", "LRU");
          //根据eviction属性,找到装饰器
          Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
          //读取flushInterval属性,既缓存的刷新周期
          Long flushInterval = context.getLongAttribute("flushInterval");
          //读取size属性,既缓存的容量大小
          Integer size = context.getIntAttribute("size");
         //读取readOnly属性,既缓存的是否只读
          boolean readWrite = !context.getBooleanAttribute("readOnly", false);
          //读取blocking属性,既缓存的是否阻塞
          boolean blocking = context.getBooleanAttribute("blocking", false);
          Properties props = context.getChildrenAsProperties();
          //通过builderAssistant创建缓存对象,并添加至configuration
          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) {
    	//经典的建造起模式,创建一个cache对象
        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 = cache;
        return cache;
      }
    
      public void addCache(Cache cache) {
        caches.put(cache.getId(), cache);
      }
    

    从这里我们可以看到默认创建了PerpetualCache对象,这个是缓存的基本实现类,然后根据配置给缓存加上装饰器,默认会装饰LRU。配置解析完成后,才会通过MapperBuilderAssistant类真正创建缓存对象并添加到Configuration对象中。为什么这里要通过MapperBuilderAssistant对象创建缓存对象呢?从名字可以看出它是XMLMapperBuilder的协助者,因为XML的解析和配置对象的装填是非常繁琐的一个过程,如果全部由一个类来完成,会非常的臃肿难看,并且耦合性较高,所以这里又雇佣了一个“协助者”。

    2. resultMapElements

      private void resultMapElements(List<XNode> list) throws Exception {
    	//遍历所有的resultmap节点
        for (XNode resultMapNode : list) {
          try {
        	 //解析具体某一个resultMap节点
            resultMapElement(resultMapNode);
          } catch (IncompleteElementException e) {
            // ignore, it will be retried
          }
        }
      }
    
      private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
        ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
        //获取resultmap节点的id属性
        String id = resultMapNode.getStringAttribute("id",
            resultMapNode.getValueBasedIdentifier());
        //获取resultmap节点的type属性
        String type = resultMapNode.getStringAttribute("type",
            resultMapNode.getStringAttribute("ofType",
                resultMapNode.getStringAttribute("resultType",
                    resultMapNode.getStringAttribute("javaType"))));
        //获取resultmap节点的extends属性,描述继承关系
        String extend = resultMapNode.getStringAttribute("extends");
        //获取resultmap节点的autoMapping属性,是否开启自动映射
        Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
        //从别名注册中心获取entity的class对象
        Class<?> typeClass = resolveClass(type);
        Discriminator discriminator = null;
        //记录子节点中的映射结果集合
        List<ResultMapping> resultMappings = new ArrayList<>();
        resultMappings.addAll(additionalResultMappings);
        //从xml文件中获取当前resultmap中的所有子节点,并开始遍历
        List<XNode> resultChildren = resultMapNode.getChildren();
        for (XNode resultChild : resultChildren) {
          if ("constructor".equals(resultChild.getName())) {//处理<constructor>节点
            processConstructorElement(resultChild, typeClass, resultMappings);
          } else if ("discriminator".equals(resultChild.getName())) {//处理<discriminator>节点
            discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
          } else {//处理<id> <result> <association> <collection>节点
            List<ResultFlag> flags = new ArrayList<>();
            if ("id".equals(resultChild.getName())) {
              flags.add(ResultFlag.ID);//如果是id节点,向flags中添加元素
            }
            //创建ResultMapping对象并加入resultMappings集合中
            resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
          }
        }
        //实例化resultMap解析器
        ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
        try {
          //通过resultMap解析器实例化resultMap并将其注册到configuration对象
          return resultMapResolver.resolve();
        } catch (IncompleteElementException  e) {
          configuration.addIncompleteResultMap(resultMapResolver);
          throw e;
        }
      }
    

    这个方法同样先是解析节点的属性,然后通过buildResultMappingFromContext方法创建ResultMapping对象并封装到ResultMapResolver中去,最后还是通过MapperBuilderAssistant实例化ResultMap对象并添加到ConfigurationresultMaps属性中:

      public ResultMap addResultMap(
          String id,
          Class<?> type,
          String extend,
          Discriminator discriminator,
          List<ResultMapping> resultMappings,
          Boolean autoMapping) {
    	 //完善id,id的完整格式是"namespace.id"
        id = applyCurrentNamespace(id, false);
        //获得父类resultMap的完整id
        extend = applyCurrentNamespace(extend, true);
    
        //针对extend属性的处理
        if (extend != null) {
          if (!configuration.hasResultMap(extend)) {
            throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
          }
          ResultMap resultMap = configuration.getResultMap(extend);
          List<ResultMapping> extendedResultMappings = new ArrayList<>(resultMap.getResultMappings());
          extendedResultMappings.removeAll(resultMappings);
          // Remove parent constructor if this resultMap declares a constructor.
          boolean declaresConstructor = false;
          for (ResultMapping resultMapping : resultMappings) {
            if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
              declaresConstructor = true;
              break;
            }
          }
          if (declaresConstructor) {
            Iterator<ResultMapping> extendedResultMappingsIter = extendedResultMappings.iterator();
            while (extendedResultMappingsIter.hasNext()) {
              if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) {
                extendedResultMappingsIter.remove();
              }
            }
          }
          //添加需要被继承下来的resultMapping对象结合
          resultMappings.addAll(extendedResultMappings);
        }
        //通过建造者模式实例化resultMap,并注册到configuration.resultMaps中
        ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
            .discriminator(discriminator)
            .build();
        configuration.addResultMap(resultMap);
        return resultMap;
      }
    

    3. sqlElement

    解析SQL节点,只是将其缓存到XMLMapperBuildersqlFragments属性中。

    4. buildStatementFromContext

      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) {
          //创建XMLStatementBuilder 专门用于解析sql语句节点
          final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
          try {
        	//解析sql语句节点
            statementParser.parseStatementNode();
          } catch (IncompleteElementException e) {
            configuration.addIncompleteStatement(statementParser);
          }
        }
      }
    

    这个方法是重点,通过XMLStatementBuilder对象解析select、update、insert、delete节点:

      public void parseStatementNode() {
    	//获取sql节点的id
        String id = context.getStringAttribute("id");
        String databaseId = context.getStringAttribute("databaseId");
    
        if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
          return;
        }
        /*获取sql节点的各种属性*/
        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);
    
        Class<?> resultTypeClass = resolveClass(resultType);
        String resultSetType = context.getStringAttribute("resultSetType");
        StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
        ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
    
        
        
        //根据sql节点的名称获取SqlCommandType(INSERT, UPDATE, DELETE, SELECT)
        String nodeName = context.getNode().getNodeName();
        SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
        boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
        boolean useCache = context.getBooleanAttribute("useCache", isSelect);
        boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
    
        // Include Fragments before parsing
        //在解析sql语句之前先解析<include>节点
        XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
        includeParser.applyIncludes(context.getNode());
    
        // Parse selectKey after includes and remove them.
        //在解析sql语句之前,处理<selectKey>子节点,并在xml节点中删除
        processSelectKeyNodes(id, parameterTypeClass, langDriver);
        
        // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
        //解析sql语句是解析mapper.xml的核心,实例化sqlSource,使用sqlSource封装sql语句
        SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
        String resultSets = context.getStringAttribute("resultSets");//获取resultSets属性
        String keyProperty = context.getStringAttribute("keyProperty");//获取主键信息keyProperty
        String keyColumn = context.getStringAttribute("keyColumn");///获取主键信息keyColumn
        
        //根据<selectKey>获取对应的SelectKeyGenerator的id
        KeyGenerator keyGenerator;
        String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
        keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
        
        
        //获取keyGenerator对象,如果是insert类型的sql语句,会使用KeyGenerator接口获取数据库生产的id;
        if (configuration.hasKeyGenerator(keyStatementId)) {
          keyGenerator = configuration.getKeyGenerator(keyStatementId);
        } else {
          keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
              configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
              ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
        }
    
        //通过builderAssistant实例化MappedStatement,并注册至configuration对象
        builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
            fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
            resultSetTypeEnum, flushCache, useCache, resultOrdered, 
            keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
      }
    
      public MappedStatement addMappedStatement(
          String id,
          SqlSource sqlSource,
          StatementType statementType,
          SqlCommandType sqlCommandType,
          Integer fetchSize,
          Integer timeout,
          String parameterMap,
          Class<?> parameterType,
          String resultMap,
          Class<?> resultType,
          ResultSetType resultSetType,
          boolean flushCache,
          boolean useCache,
          boolean resultOrdered,
          KeyGenerator keyGenerator,
          String keyProperty,
          String keyColumn,
          String databaseId,
          LanguageDriver lang,
          String resultSets) {
    
        if (unresolvedCacheRef) {
          throw new IncompleteElementException("Cache-ref not yet resolved");
        }
    
        id = applyCurrentNamespace(id, false);
        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    
        MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
            .resource(resource)
            .fetchSize(fetchSize)
            .timeout(timeout)
            .statementType(statementType)
            .keyGenerator(keyGenerator)
            .keyProperty(keyProperty)
            .keyColumn(keyColumn)
            .databaseId(databaseId)
            .lang(lang)
            .resultOrdered(resultOrdered)
            .resultSets(resultSets)
            .resultMaps(getStatementResultMaps(resultMap, resultType, id))
            .resultSetType(resultSetType)
            .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
            .useCache(valueOrDefault(useCache, isSelect))
            .cache(currentCache);
    
        ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
        if (statementParameterMap != null) {
          statementBuilder.parameterMap(statementParameterMap);
        }
    
        MappedStatement statement = statementBuilder.build();
        configuration.addMappedStatement(statement);
        return statement;
      }
    

    同样的,最后根据sql语句和属性实例化MappedStatement对象,并添加到Configuration对象的mappedStatements属性中。

    回到XMLMapperBuilder.parse方法中,在解析完xml之后又调用了bindMapperForNamespace方法:

      private void bindMapperForNamespace() {
    	//获取命名空间
        String namespace = builderAssistant.getCurrentNamespace();
        if (namespace != null) {
          Class<?> boundType = null;
          try {
        	//通过命名空间获取mapper接口的class对象
            boundType = Resources.classForName(namespace);
          } catch (ClassNotFoundException e) {
            //ignore, bound type is not required
          }
          if (boundType != null) {
            if (!configuration.hasMapper(boundType)) {//是否已经注册过该mapper接口?
              // Spring may not know the real resource name so we set a flag
              // to prevent loading again this resource from the mapper interface
              // look at MapperAnnotationBuilder#loadXmlResource
              //将命名空间添加至configuration.loadedResource集合中
              configuration.addLoadedResource("namespace:" + namespace);
              //将mapper接口添加到mapper注册中心
              configuration.addMapper(boundType);
            }
          }
        }
      }
    

    这个方法中首先通过namespace拿到xml对应的Mapper接口类型,然后委托给Configuration类中的mapperRegistry注册动态的代理的工厂MapperProxyFactory

      public <T> void addMapper(Class<T> type) {
        if (type.isInterface()) {
            if (hasMapper(type)) {
              throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
            }
          boolean loadCompleted = false;
          try {
        	//实例化Mapper接口的代理工程类,并将信息添加至knownMappers
            knownMappers.put(type, new MapperProxyFactory<T>(type));
            // It's important that the type is added before the parser is run
            // otherwise the binding may automatically be attempted by the
            // mapper parser. If the type is already known, it won't try.
            //解析接口上的注解信息,并添加至configuration对象
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
            parser.parse();
            loadCompleted = true;
          } finally {
            if (!loadCompleted) {
              knownMappers.remove(type);
            }
          }
        }
      }
    

    这个就是创建Mapper接口的动态代理对象的工厂类,所以Mapper的代理对象实际上并不是在启动的时候就创建好了,而是在方法调用时才会创建,为什么会这么设计呢?因为代理对象和SqlSession是一一对应的,而我们每一次调用Mapper的方法都是创建一个新的SqlSession,所以这里只是缓存了代理工厂对象。
    代理工厂注册之后还通过MapperAnnotationBuilder类提供了对注解方式的支持,这里就不阐述了,结果就是将注解的值添加到Configuration中去。

    总结

    解析配置文件的流程虽然比较长,但逻辑一点都不复杂,主要就是获取xml配置的属性值,实例化不同的配置对象,并将这些配置都丢到Configuration对象中去,我们只需要重点关注哪些对象被注册到了Configuration中去,最后根据Configuration对象实例化DefaultSqlSessionFactory对象并返回,而DefaultSqlSessionFactory就是用来创建SqlSession对象的,这个对象就是上一篇架构图中的接口层,它提供了所有访问数据库的操作并屏蔽了底层复杂实现细节,具体的实现原理将在下一篇进行分析。

  • 相关阅读:
    从URL输入到页面展现,过程中发生了什么?
    Android ADB被占用 重启 ADB方法
    Android消息处理:EventBus、BroadCast和Handler-优缺点比较
    Android EventBus 的使用
    浅谈Java/Android下的注解
    如何理解Android中的xmlns
    【LeetCode】165
    【leetcode】155
    【LeetCode】12 & 13
    【LeetCode】66 & 67- Plus One & Add Binary
  • 原文地址:https://www.cnblogs.com/yewy/p/13260038.html
Copyright © 2020-2023  润新知