加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 大数据 > 正文

mybatis源码配置文件解析之五:解析mappers标签(解析class属性

发布时间:2020-12-14 18:03:39 所属栏目:大数据 来源:网络整理
导读:在上篇文章中分析了mybatis解析mapper标签中的resource、url属性的过程,《mybatis源码配置文件解析之五:解析mappers标签(解析XML映射文件)》。通过分析可以知道在解析这两个属性的时候首先解析的是对应的XML映射文件,然后解析XML映射文件中的namespace

在上篇文章中分析了mybatis解析mapper标签中的resource、url属性的过程,《mybatis源码配置文件解析之五:解析mappers标签(解析XML映射文件)》。通过分析可以知道在解析这两个属性的时候首先解析的是对应的XML映射文件,然后解析XML映射文件中的namespace属性配置的接口,在上篇中说到该解析过程和mapper标签中的class属性的解析过程是一样的,因为class属性配置的即是一个接口的全限类名。

一、概述

在mybatis的核心配置文件中配置mappers标签有以下方式,

<mappers>
        mapper class="cn.com.mybatis.dao.UserMapper"/> 
</>

上面这种方式便是mapper标签的class属性配置方式,其解析部分过程如下,

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

可以看到主要是调用了configuration.addMapper方法,和上篇文章中解析namespace调用的方法是一致的。看其具体实现

public <T> void addMapper(Class<T> type) {
    mapperRegistry.addMapper(type);
  }

下方分析mapperRegistry.addMapper方法。

二、详述

mapperRegistry.addMapper方法的定义如下,

  type) {
    if (type.isInterface()) {//判断是否为接口
      if (hasMapper(type)) {如果knownMappers中已经存在该type,则抛出异常
        new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
          1、把type放入knownMappers中,其value为一个MapperProxyFactory对象
        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.
        2、对mapper文件及注解进行解析,初始化了sqlAnnotationTypessqlProviderAnnotationTypes两个变量
      //具体的解析过程,1、先解析对应的XML映射文件,2、再解析接口方法中的注解信息
        /**sqlAnnotationTypes.add(Select.class);
    sqlAnnotationTypes.add(Insert.class);
    sqlAnnotationTypes.add(Update.class);
    sqlAnnotationTypes.add(Delete.class);

    sqlProviderAnnotationTypes.add(SelectProvider.class);
    sqlProviderAnnotationTypes.add(InsertProvider.class);
    sqlProviderAnnotationTypes.add(UpdateProvider.class);
    sqlProviderAnnotationTypes.add(DeleteProvider.class);
         * 
         */
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config,type);
        
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {//3、如果解析失败,则删除knowMapper中的信息
          knownMappers.remove(type);
        }
      }
    }
  }

该方法主要分为下面几个步骤。

1、检查是否解析过接口

首先会判断knowMappers中是否已经存在该接口,如果存在则会抛出异常

);
      }

如果不存在则放入knownMappers中,

 new MapperProxyFactory<T>(type));

继续解析对应的映射文件及接口方法注解

2、解析接口对应的映射文件及接口方法注解

上面把mapper接口放入了knownMappers中,接着需要解析映射文件及注解,

2、对mapper文件及注解进行解析,初始化了sqlAnnotationTypessqlProviderAnnotationTypes两个变量
        具体的解析过程,1、先解析对应的XML映射文件,2、再解析接口方法中的注解信息
        true;

上面的代码,生成了一个MapperAnnotationBuilder实例,

public MapperAnnotationBuilder(Configuration configuration,Class<?> type) {
    String resource = type.getName().replace('.','/') + ".java (best guess)";
    this.assistant =  MapperBuilderAssistant(configuration,resource);
    this.configuration = configuration;
    this.type = type;

    sqlAnnotationTypes.add(Select.);
    sqlAnnotationTypes.add(Insert.);
    sqlAnnotationTypes.add(Update.);
    sqlAnnotationTypes.add(Delete.);

    sqlProviderAnnotationTypes.add(SelectProvider.);
    sqlProviderAnnotationTypes.add(InsertProvider.);
    sqlProviderAnnotationTypes.add(UpdateProvider.);
    sqlProviderAnnotationTypes.add(DeleteProvider.);
  }

给sqlAnnotationTypes和sqlProviderAnnotationTypes进行了赋值。

下面看具体的解析过程,

parser.parse();

MapperAnnotationBuilder的parse方法如下,

public void parse() {
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {//判断是否加载过该Mapper接口
        解析和接口同名的xml文件,前提是存在该文件,如果不存在该文件要怎么解析那?答案是解析接口中方法上的注解
        
         * 1、解析和接口同名的xml配置文件,最终要做的是把xml文件中的标签,转化为mapperStatement,
         * 并放入mappedStatements中
         * 
         
      loadXmlResource();
      configuration.addLoadedResource(resource);
      assistant.setCurrentNamespace(type.getName());
      解析接口上的@CacheNamespace注解
      parseCache();
      parseCacheRef();
      2、获得接口中的所有方法,并解析方法上的注解
      Method[] methods = type.getMethods();
      for (Method method : methods) {
         issue #237
          method.isBridge()) {
              解析方法上的注解
            parseStatement(method);
          }
        } catch (IncompleteElementException e) {
          configuration.addIncompleteMethod(new MethodResolver(this,method));
        }
      }
    }
    parsePendingMethods();
  }

首先判断是否加载过该资源,

configuration.isResourceLoaded(resource)) {

}

只有未加载过,才会执行该方法的逻辑,否则该方法执行完毕。

boolean isResourceLoaded(String resource) {
    return loadedResources.contains(resource);
  }

从loadResources中进行判断,判断是否解析过该Mapper接口,答案是没有解析过,则会继续解析。

1.1、解析对应的XML文件

首先会解析XML文件,调用下面的方法,


      loadXmlResource();

看loadXmlResource方法


 * 解析mapper配置文件
 */
  private  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())) {
        解析对应的XML映射文件,其名称为接口类+"."+xml,即和接口类同名且在同一个包下。
      String xmlResource = type.getName().replace('.','/') + ".xml";
      InputStream inputStream =  {
        inputStream = Resources.getResourceAsStream(type.getClassLoader(),xmlResource);
      }  (IOException e) {
         ignore,resource is not required
      }
      if (inputStream != ) {
        XMLMapperBuilder xmlParser =  XMLMapperBuilder(inputStream,assistant.getConfiguration(),xmlResource,configuration.getSqlFragments(),type.getName());
        解析xml映射文件
        xmlParser.parse();
      }
    }
  }

首先进行了判断,进入if判断,看判断上的注解

 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

第一句注解没理解什么意思,第二句的意思是方式两次加载资源,第三句是说明了该标识是在XMLMapperBuilder类中的bindMapperForNamespace中进行的设置,如下

为什么这样设置,后面会总结mapper的加载流程详细说明该问题。

判断之后寻找相应的XML映射文件,映射文件的文件路径如下,


从上面可以看出Mapper接口文件和XML映射文件在同一个包下,且文件名称相同(扩展名不同)。接着便是解析XML映射文件的逻辑。

        xmlParser.parse();
      }

该逻辑和《mybatis源码配置文件解析之五:解析mappers标签(解析XML映射文件)》的过程是一样的,调用XMLMapperBuilder的parse方法进行解析,解析的结果为MapperStatement对象。

1.2、解析接口方法上的注解

上面是解析接口对应的XML映射文件,解析完成之后,还要解析接口方法上的注解,因为mybatis的sql配置有两种方式,一种是通过XML映射文件,另一种便是注解(当SQL比较复杂建议使用映射文件的方式),下面看解析注解的过程,


 parseStatement(Method method) {
    Class<?> parameterTypeClass = getParameterType(method);
    LanguageDriver languageDriver = getLanguageDriver(method);
    获得方法上的注解,并生成SqlSource
    SqlSource sqlSource = getSqlSourceFromAnnotations(method,parameterTypeClass,languageDriver);
    if (sqlSource != ) {
      Options options = method.getAnnotation(Options.);
      生成mappedStatementId,为接口的权限类名+方法名。从这里可以得出同一个接口或namespace中不允许有同名的方法名或id
      final String mappedStatementId = type.getName() + "." + method.getName();
      Integer fetchSize = ;
      Integer timeout = ;
      StatementType statementType = StatementType.PREPARED;
      ResultSetType resultSetType = ResultSetType.FORWARD_ONLY;
      SqlCommandType sqlCommandType = getSqlCommandType(method);
      boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
      boolean flushCache = !isSelect;
      boolean useCache = isSelect;

      KeyGenerator keyGenerator;
      String keyProperty = "id";
      String keyColumn = if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
         first check for SelectKey annotation - that overrides everything else
        SelectKey selectKey = method.getAnnotation(SelectKey.);
        if (selectKey != ) {
          keyGenerator = handleSelectKeyAnnotation(selectKey,mappedStatementId,getParameterType(method),languageDriver);
          keyProperty = selectKey.keyProperty();
        } if (options == ) {
          keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
        }  {
          keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
          keyProperty = options.keyProperty();
          keyColumn = options.keyColumn();
        }
      }  {
        keyGenerator = NoKeyGenerator.INSTANCE;
      }

      if (options != ) {
        if (FlushCachePolicy.TRUE.equals(options.flushCache())) {
          flushCache = ;
        }  (FlushCachePolicy.FALSE.equals(options.flushCache())) {
          flushCache = ;
        }
        useCache = options.useCache();
        fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; issue #348
        timeout = options.timeout() > -1 ? options.timeout() : ;
        statementType = options.statementType();
        resultSetType = options.resultSetType();
      }

      String resultMapId = ;
      ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.if (resultMapAnnotation != ) {
        String[] resultMaps = resultMapAnnotation.value();
        StringBuilder sb =  StringBuilder();
         (String resultMap : resultMaps) {
          if (sb.length() > 0) {
            sb.append(",");
          }
          sb.append(resultMap);
        }
        resultMapId = sb.toString();
      }  (isSelect) {
        resultMapId = parseResultMap(method);
      }

      assistant.addMappedStatement(
          mappedStatementId,sqlSource,statementType,sqlCommandType,fetchSize,timeout, ParameterMapID
           TODO gcode issue #577
           DatabaseID
           ResultSets
          options != null ? nullOrEmpty(options.resultSets()) : );
    }
  }

注解的解析和解析XML映射文件的方式是一样的,解析的属性是一致的。需要注意下面的注解

@SelectProvider(type=BaseUserProvider.class,method="getUser")

该注解的意思是定义select语句的提供者,需要配置type和method,即提供类的Class对象和相应的方法(返回一个字符串)

3、解析失败回退

如果在继续过程中失败或抛出异常,则进行回退,回退的意思是从knownMappers中删除该类型。

3、如果解析失败,则删除knowMapper中的信息
          knownMappers.remove(type);
        }
      }

因为Mapper解析的过程有两个结果一个是放入到configuration.knownMappers中的MapperProxyFactory对象,一个是放入到configuration.mappedStatements中MappedStatement对象,由于生产MappedStatement对象失败,所以要回退生成MapperProxyFactory对象过程。

三、总结

本文分析了mybatis解析<mapper class=""/>的过程,依旧是包含MapperProxyFactory和MappedStatement两个过程。

?

有不当之处,欢迎指正,感谢!

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读