zoukankan      html  css  js  c++  java
  • MyBatis启动:MapperStatement创建

    参考:http://blog.csdn.net/ashan_li/article/details/50351080

    MappedStatement说明

    一个MappedStatement对象对应Mapper配置文件中的一个select/update/insert/delete节点,主要描述的是一条SQL语句。其属性有

    //节点中的id属性加要命名空间  
    private String id;  
    //直接从节点属性中取  
    private Integer fetchSize;  
    //直接从节点属性中取  
    private Integer timeout;  
    private StatementType statementType;  
    private ResultSetType resultSetType;  
    //对应一条SQL语句  
    private SqlSource sqlSource;  
      
    //每条语句都对就一个缓存,如果有的话。  
    private Cache cache;  
    //这个已经过时了  
    private ParameterMap parameterMap;  
    private List<ResultMap> resultMaps;  
    private boolean flushCacheRequired;  
    private boolean useCache;  
    private boolean resultOrdered;  
    //SQL的类型,select/update/insert/detete  
    private SqlCommandType sqlCommandType;  
    private KeyGenerator keyGenerator;  
    private String[] keyProperties;  
    private String[] keyColumns;  
      
    //是否有内映射  
    private boolean hasNestedResultMaps;  
    private String databaseId;  
    private Log statementLog;  
    private LanguageDriver lang;  
    private String[] resultSets; 
    

      

    Mapper是接口,用来声明持久层的方法,而Mapper配置对应的XML,决定了方法的执行的内容,决定持久层方法的行为。在MyBatis启 动时,会解析这些包含SQL的XML文件,并将其包装成为MapperStatement对象,并将MapperStatement注册到全局的 configuration对象上,接下来就深入的了解代码的实现。

    private void mapperElement(XNode parent) throws Exception {  
        if (parent != null) {  
          for (XNode child : parent.getChildren()) {  
            if ("package".equals(child.getName())) {  
              String mapperPackage = child.getStringAttribute("name");  
              configuration.addMappers(mapperPackage);  
            } else {  
              String resource = child.getStringAttribute("resource");  
              String url = child.getStringAttribute("url");  
              String mapperClass = child.getStringAttribute("class");  
              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<?> mapperInterface = Resources.classForName(mapperClass);  
                configuration.addMapper(mapperInterface);  
              } else {  
                throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");  
              }  
            }  
          }  
        }  
      }  
    

      

    从 源码中就可以看出,配置Mapper时,可以配置package熟悉,注册包下所有的接口。还可以从资源中比如硬盘上,网络中,去加载XML文件。注册过 程是通过注册器MapperRegistry来完成的。注册的容器是一个map,Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();。

    key是mapper的接口完整类名,value是mapper的代理工厂。注册完成后,还要做解析XML文件操作。

    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 {  
          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.  
          MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);  
          parser.parse();  
          loadCompleted = true;  
        } finally {  
          if (!loadCompleted) {  
            knownMappers.remove(type);  
          }  
        }  
      }  
    }
    

      

     

      

    下面 是解析的代码

    public void parse() {  
        String resource = type.toString();  
        if (!configuration.isResourceLoaded(resource)) {  
          loadXmlResource();  
          configuration.addLoadedResource(resource);  
          assistant.setCurrentNamespace(type.getName());  
          parseCache();  
          parseCacheRef();  
          Method[] methods = type.getMethods();  
          for (Method method : methods) {  
            try {  
              parseStatement(method);  
            } catch (IncompleteElementException e) {  
              configuration.addIncompleteMethod(new MethodResolver(this, method));  
            }  
          }  
        }  
        parsePendingMethods();  
      }  
    

      

     
    private void loadXmlResource() {  
      // Spring may not know the real resource name so we check a flag  
      // to prevent loading again a resource twice  
      // this flag is set at XMLMapperBuilder#bindMapperForNamespace  
      if (!configuration.isResourceLoaded("namespace:" + type.getName())) {  
        String xmlResource = type.getName().replace('.', '/') + ".xml";  
        InputStream inputStream = null;  
        try {  
          inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);  
        } catch (IOException e) {  
          // ignore, resource is not required  
        }  
        if (inputStream != null) {  
          XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());  
          xmlParser.parse();  
        }  
      }  
    }  
    

      

     

     

    MyBatis通过替换mapper完整类名中的“.”,替换成为“/”,然后加上后缀“.xml”,拼成XML资源路径,然后判断是否已加载过XML,没有的话加载XML文件,然后使用xmlMapperBuilder建造者解析XML中的元素。


     
    public void parse() {  
      if (!configuration.isResourceLoaded(resource)) {  
        configurationElement(parser.evalNode("/mapper"));  
        configuration.addLoadedResource(resource);  
        bindMapperForNamespace();  
      }  
      
      parsePendingResultMaps();  
      parsePendingChacheRefs();  
      parsePendingStatements();  
    }  
    

      

    resource是创建建造者的构造参数,type.getClass(),就是mapper的类型。判断然后还没有加载mapper,就开始解析XML文件中的mapper节点。

    private void configurationElement(XNode context) {  
        try {  
          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"));  
          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. Cause: " + e, e);  
        }  
      }  
    

      

     

     

    解 析时,先设置命名空间。然后解析cache-ref元素,可以使用其他命名空间的的缓存。在configuration对象上有一个 cacheRefMap用来维护引用缓存的关系。并且引用其他命名空间的引用指向助手类的currCache属性上。如果被指向的命名空间还未加载,则抛 出异常,并且往configuration对象上添加未处理的缓存引用chcheRef。

    private void cacheRefElement(XNode context) {  
      if (context != null) {  
        configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));  
        CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));  
        try {  
          cacheRefResolver.resolveCacheRef();  
        } catch (IncompleteElementException e) {  
          configuration.addIncompleteCacheRef(cacheRefResolver);  
        }  
      }  
    }  
    

      

    解析缓存元素,可以使用type属性配置自定义的缓存,否则使用默认 的PERPETUAL。然后用别名注册器注册缓存类。接下来注册缓存的回收算法,缓存大小,过期时间,是否只读等属性。然后由助手类通过反射创建一个具体 的Cache对象。然后注册到configuration全局对象上。

    private void cacheElement(XNode context) throws Exception {  
      if (context != null) {  
        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);  
        Properties props = context.getChildrenAsProperties();  
        builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, props);  
      }  
    }  
    

      

     

     

    下一步是解析parameterMap,新版中已经不推荐配置这个属性了,属于老方法。

    参数Map映射已经被淘汰,但是结果集映射还很有用。接下来就是解析 resultMap。解析resultMap的元素比较多,解析完成后,还会根据解析到的映射关系创建一个结果处理器对象 resultMapResolver,后面对数据库操作时,用来处理列和属性的类型转换。

    private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {  
      ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());  
      String id = resultMapNode.getStringAttribute("id",  
          resultMapNode.getValueBasedIdentifier());  
      String type = resultMapNode.getStringAttribute("type",  
          resultMapNode.getStringAttribute("ofType",  
              resultMapNode.getStringAttribute("resultType",  
                  resultMapNode.getStringAttribute("javaType"))));  
      String extend = resultMapNode.getStringAttribute("extends");  
      Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");  
      Class<?> typeClass = resolveClass(type);  
      Discriminator discriminator = null;  
      List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();  
      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 {  
          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 {  
        return resultMapResolver.resolve();  
      } catch (IncompleteElementException  e) {  
        configuration.addIncompleteResultMap(resultMapResolver);  
        throw e;  
      }  
    }  
    

      

     

     

    解析来继续解析SQL片段,用来复用的SQL。助手类会将SQL片段的ID前面加上当前命名空间和一个点,用来和其他命名空间区别开。然后将SQL片段加载到configuration全局对象的sqlFragments对象上保存。

    private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception {  
      for (XNode context : list) {  
        String databaseId = context.getStringAttribute("databaseId");  
        String id = context.getStringAttribute("id");  
        id = builderAssistant.applyCurrentNamespace(id, false);  
        if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) sqlFragments.put(id, context);  
      }  
    }  
    

      

     

     

    最后是重头戏,解析增删改查节点,创建Statement对象。同样是通过建造者模式来创建语句对象,建造者的构造参数包括全局配置信息,当前命名空间助手,XML配置信息和数据库ID。

    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);  
        }  
      }  
    }  
    

      

     

    首先还是解析XML文件的各个属性,然后处理<include>和<selectKey>片段。根据include标签中的refid到全局配置中取对应的SQL片段。根据selectKey的配置信息,创建一个MapperStatement,并且添加到全局配置中,然后移除selectKey节点。

    public void parseStatementNode() {  
      String id = context.getStringAttribute("id");  
      String databaseId = context.getStringAttribute("databaseId");  
      
      if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) return;  
      
      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);  
      
      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  
      XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);  
      includeParser.applyIncludes(context.getNode());  
      
      // Parse selectKey after includes and remove them.  
      processSelectKeyNodes(id, parameterTypeClass, langDriver);  
        
      // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)  
      SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);  
      String resultSets = context.getStringAttribute("resultSets");  
      String keyProperty = context.getStringAttribute("keyProperty");  
      String keyColumn = context.getStringAttribute("keyColumn");  
      KeyGenerator keyGenerator;  
      String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;  
      keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);  
      if (configuration.hasKeyGenerator(keyStatementId)) {  
        keyGenerator = configuration.getKeyGenerator(keyStatementId);  
      } else {  
        keyGenerator = context.getBooleanAttribute("useGeneratedKeys",  
            configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))  
            ? new Jdbc3KeyGenerator() : new NoKeyGenerator();  
      }  
      
      builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,  
          fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,  
          resultSetTypeEnum, flushCache, useCache, resultOrdered,   
          keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);  
    }  
    

      

     

     

    接下来的操作,也是根据配置的属性,然后通过建造者创建mappedStatement对象。并添加到configuration全局对象上。

  • 相关阅读:
    Pandas高级教程之:category数据类型
    Pandas高级教程之:处理缺失数据
    Pandas高级教程之:处理text数据
    密码学系列之:blowfish对称密钥分组算法
    架构之:数据流架构
    ES6中的新特性:Iterables和iterators
    密码学系列之:feistel cipher
    Pandas高级教程之:Dataframe的重排和旋转
    Electron实用技巧-electron-builder中用户协议(license)的使用及多语言支持
    Electron实用技巧-开机启动时隐藏主窗口,只显示系统托盘
  • 原文地址:https://www.cnblogs.com/qlqwjy/p/7866982.html
Copyright © 2011-2022 走看看