Mybatis 基于注解Mapper源码分析

Posted wei_zw

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Mybatis 基于注解Mapper源码分析相关的知识,希望对你有一定的参考价值。

     目前Mybatis除了可以通过XML配置SQL外还可以通过注解的形式配置SQL,本文中主要介绍了Mybatis是如何处理注解SQL映射的,通过源码分析处理过程

 XML配置

<configuration>
	<settings>
		<setting name="defaultExecutorType" value="SIMPLE"/>
		<setting name="useGeneratedKeys" value="true"/>
	</settings>
	
	<typeAliases>
		<typeAlias type="org.apache.ibatis.submitted.blocking_cache.Person" alias="Person" /> 
	</typeAliases>
	
	<environments default="development">
		<environment id="development">
			<transactionManager type="JDBC">
				<property name="" value="" />
			</transactionManager>
			<dataSource type="UNPOOLED">
				<property name="driver" value="org.hsqldb.jdbcDriver" />
				<property name="url" value="jdbc:hsqldb:mem:cache" />
				<property name="username" value="sa" />
			</dataSource>
		</environment>
	</environments>
	
	<mappers>
		<mapper class="org.apache.ibatis.submitted.blocking_cache.PersonMapper"/>	
	</mappers>
</configuration> 

 

 @Insert("insert into table2 (name) values(#{name})")
    @SelectKey(statement="call identity()", keyProperty="nameId", before=false, resultType=int.class)
    int insertTable2(Name name);
    
    @Insert("insert into table2 (name) values(#{name})")
    @Options(useGeneratedKeys=true, keyProperty="nameId,generatedName", keyColumn="ID,NAME_FRED")
    int insertTable2WithGeneratedKey(Name name);

  

 解析过程 

 

 

  private void mapperElement(XNode parent) throws Exception {
    //如果configuration中配置了mapper节点
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        //如果配置对是包路径
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          //获取mapper元素中的resource属性
          String resource = child.getStringAttribute("resource");
          //获取mapper元素中的url属性
          String url = child.getStringAttribute("url");
          //获取mapper元素中的class属性,如果基于注解的配置的mapper 配置的就是class
          String mapperClass = child.getStringAttribute("class");
          //对于resource,url,mapperClass 优先使用resource,其次是url最后是class
          if (resource != null && url == null && mapperClass == null) {
            //创建异常上下文
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            //创建XMLMapperBuilder
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            //解析XML
            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) {
            //获取mapper的接口
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            //将该接口注册到已知mapper
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

  

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

  

//注册Mapper
  public <T> void addMapper(Class<T> type) {
    //如果type是接口
    if (type.isInterface()) {
      //判断该接口是否已经注册过相应的Mapper了,如果是则抛出异常,因为knownMappers的key为type
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        //对该接口创建MapperProxyFactory,并保注册到knownMappers
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        //创建MapperAnnotationBuilder
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        //解析Annotation
        parser.parse();
        //解析成功则表示加载完成
        loadCompleted = true;
      } finally {
        //如果加载没有完成则将其从knownMappers中删除
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

  

public MapperAnnotationBuilder(Configuration configuration, Class<?> type) {
  String resource = type.getName().replace(\'.\', \'/\') + ".java (best guess)";
  this.assistant = new MapperBuilderAssistant(configuration, resource);
  this.configuration = configuration;
  this.type = type;
  //初始化注解类型,分为两组
  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);
}

public void parse() {
  String resource = type.toString();
  //判断该资源是否已经注册
  if (!configuration.isResourceLoaded(resource)) {
    //没有注册过则需要加载XML资源
    loadXmlResource();
    //将该资源名称添加到已经注册集合中
    configuration.addLoadedResource(resource);
    //设置nameSpace
    assistant.setCurrentNamespace(type.getName());
    //解析cache
    parseCache();
    //解析cacheRef
    parseCacheRef();
    //获取
    Method[] methods = type.getMethods();
    for (Method method : methods) {
      try {
        //如果不是bridge方法则解析statement
        if (!method.isBridge()) {
          parseStatement(method);
        }
      } catch (IncompleteElementException e) {
        configuration.addIncompleteMethod(new MethodResolver(this, method));
      }
    }
  }
  //解析待定方法
  parsePendingMethods();
}

  

如果存在XML配置也是会加载的  

private void loadXmlResource() {
    //判断资源是否有加载过
    if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
      //获取资源的path
      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
        XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
        //解析XML
        xmlParser.parse();
      }
    }
  }

  下图是同时存在XML和annotation的情况

 

 private void parseCache() {
    //获取接口上 @CacheNamespace注解
    CacheNamespace cacheDomain = type.getAnnotation(CacheNamespace.class);
    //如果注解存在
    if (cacheDomain != null) {
      //获取注解配置的缓存大小
      Integer size = cacheDomain.size() == 0 ? null : cacheDomain.size();
      //获取缓存刷新频率
      Long flushInterval = cacheDomain.flushInterval() == 0 ? null : cacheDomain.flushInterval();
      //解析CacheNamespace配置的属性
      Properties props = convertToProperties(cacheDomain.properties());
      //使用注解配置的数据创建缓存
      assistant.useNewCache(cacheDomain.implementation(), cacheDomain.eviction(), flushInterval, size, cacheDomain.readWrite(), cacheDomain.blocking(), props);
    }
  }

  

private void parseCacheRef() {
    //获取接口上 @CacheNamespaceRef 注解
    CacheNamespaceRef cacheDomainRef = type.getAnnotation(CacheNamespaceRef.class);
    //如果配置了缓存引用
    if (cacheDomainRef != null) {
      //获取引用的类型
      Class<?> refType = cacheDomainRef.value();
      String refName = cacheDomainRef.name();
      //如果引用类型和引用名称都为空则抛出异常
      if (refType == void.class && refName.isEmpty()) {
        throw new BuilderException("Should be specified either value() or name() attribute in the @CacheNamespaceRef");
      }
      //如果引用类型和引用名称同时配置了有效数据则抛出异常,这两个是互斥数据
      if (refType != void.class && !refName.isEmpty()) {
        throw new BuilderException("Cannot use both value() and name() attribute in the @CacheNamespaceRef");
      }
      //获取namespace
      String namespace = (refType != void.class) ? refType.getName() : refName;
      //使用缓存
      assistant.useCacheRef(namespace);
    }
  }

  

  void parseStatement(Method method) {
    //获取参数类型
    Class<?> parameterTypeClass = getParameterType(method);
    //获取LanguageDriver
    LanguageDriver languageDriver = getLanguageDriver(method);
    //从注解中获取Sqlsource
    SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
    //如果sqlSource不为null
    if (sqlSource != null) {
      //获取 @Options注解
      Options options = method.getAnnotation(Options.class);
      //创建statementId
      final String mappedStatementId = type.getName() + "." + method.getName();
      Integer fetchSize = null;
      Integer timeout = null;
      StatementType statementType = StatementType.PREPARED;
      ResultSetType resultSetType = ResultSetType.FORWARD_ONLY;
      //获取sql类型
      SqlCommandType sqlCommandType = getSqlCommandType(method);
      //是否是查询
      boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
      //是否需要刷新缓存,如果不是查询默认值为true,查询默认值为false
      boolean flushCache = !isSelect;
      //是否使用缓存,查询默认为true,不是查询默认为false
      boolean useCache = isSelect;

      KeyGenerator keyGenerator;
      String keyProperty = "id";
      String keyColumn = null;
      //如果是插入或更新
      if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
        //获取SelectKey注解
        SelectKey selectKey = method.getAnnotation(SelectKey.class);
        if (selectKey != null) {
          keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
          keyProperty = selectKey.keyProperty();
        } else if (options == null) {
          keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
        } else {
          keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
          keyProperty = options.keyProperty();
          keyColumn = options.keyColumn();
        }
      } else {//不是插入或更新 即查询和删除则keyGenerator为NoKeyGenerator实例
        keyGenerator = NoKeyGenerator.INSTANCE;
      }
      //如果@Options注解不为null
      if (options != null) {
        //根据配置的值设置是否刷新缓存
        if (FlushCachePolicy.TRUE.equals(options.flushCache())) {
          flushCache = true;
        } else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {
          flushCache = false;
        }
        //是否使用缓存
        useCache = options.useCache();
        //fetchSize
        fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
        timeout = options.timeout() > -1 ? options.timeout() : null;
        statementType = options.statementType();
        resultSetType = options.resultSetType();
      }

      String resultMapId = null;
      //获取ResultMap注解
      ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
      if (resultMapAnnotation != null) {
        String[] resultMaps = resultMapAnnotation.value();
        StringBuilder sb = new StringBuilder();
        for (String resultMap : resultMaps) {
          if (sb.length() > 0) {
            sb.append(",");
          }
          sb.append(resultMap);
        }
        resultMapId = sb.toString();
      } else if (isSelect) {
        resultMapId = parseResultMap(method);
      }

      assistant.addMappedStatement(
          mappedStatementId,
          sqlSource,
          statementType,
          sqlCommandType,
          fetchSize,
          timeout,
          // ParameterMapID
          null,
          parameterTypeClass,
          resultMapId,
          getReturnType(method),
          resultSetType,
          flushCache,
          useCache,
          // TODO gcode issue #577
          false,
          keyGenerator,
          keyProperty,
          keyColumn,
          // DatabaseID
          null,
          languageDriver,
          // ResultSets
          options != null ? nullOrEmpty(options.resultSets()) : null);
    }
  }

  

private SqlSource getSqlSourceFromAnnotations(Method method, Class<?> parameterType, LanguageDriver languageDriver) {
    try {
      //获取@Select, @Insert, @Update, @Delete类型的注解 
      Class<? extends Annotation> sqlAnnotationType = getSqlAnnotationType(method);
      //获取 @SelectProvider, @InsertProvider, @UpdateProvider @DeleteProvider注解
      Class<? extends Annotation> sqlProviderAnnotationType = getSqlProviderAnnotationType(method);
      //如果SQL注解不为null
      if (sqlAnnotationType != null) {
        //同时sqlProvider注解也不为空则抛出异常,两者互斥
        if (sqlProviderAnnotationType != null) {
          throw new BindingException("You cannot supply both a static SQL and SqlProvider to method named " + method.getName());
        }
        //获取注解
        Annotation sqlAnnotation = method.getAnnotation(sqlAnnotationType);
        //获取注解配置的值
        final String[] strings = (String[]) sqlAnnotation.getClass().getMethod("value").invoke(sqlAnnotation);
        //根据配置的数据创建SqlSource
        return buildSqlSourceFromStrings(strings, parameterType, languageDriver);
      } else if (sqlProviderAnnotationType != null) {//如果SqlProvider注解不为空
        Annotation sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType);
        //创建一个ProviderSqlSource
        return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation, type, method);
      }
      //如果没有配置Sql注解也没有配置SqlProvider注解则返回null
      return null;
    } catch (Exception e) {
      throw new BuilderException("Could not find value method on SQL annotation.  Cause: " + e, e);
    }
  }

  

 

以上是关于Mybatis 基于注解Mapper源码分析的主要内容,如果未能解决你的问题,请参考以下文章

Mybatis Mapper接口是如何找到实现类的-源码分析

MyBatis中的@Mapper注解及配套注解使用详解(上)

mybatis源码分析之04Mapper接口的动态代理

Mybatis的原理分析1(@Mapper是如何生效的)

spring boot整合mybatis基于注解开发以及动态sql的使用

Mybatis设计与源码分析