java源码学习-Mybatis(1)加载mapper流程

Mybatis加载mapper


首先说明一下, 我是在Springboot中使用的Mybatis所以并没有使用xml配置

在这里插入图片描述
这里我整合了一份脑图, 需要的自取思维导图
正好给我赚点积分下载资源

MapperScan

在这里插入图片描述
MapperScan这个注解的作用就是将我们的mapper包中的所有mapper接口注册入mybatis,
所以我从这里开始入手全局搜索这个MapperScan
在这里插入图片描述
上图中可以看见MapperScannerRegistrar这个类是唯一一个有使用到这个注解类的类, 这个类的作用很明显就是一个Mapper注册连接器
这个类中只有三个方法和一个内部类
在这里插入图片描述

  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
  	// 获取了MapperScan注解中的值, 我们上面的mapper包的位置就在这个注解的value中
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes
    .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    if (mapperScanAttrs != null) {
    	// 解析注解
      registerBeanDefinitions(mapperScanAttrs, registry);
    }
  }

在解析过程中主要是获取mapper
解析方法中的最后一步就是使用了ClassPathMapperScanner的doScan方法扫描

scanner.doScan(StringUtils.toStringArray(basePackages));
  @Override
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
  	// 使用的父类的doScan方法
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

    if (beanDefinitions.isEmpty()) {
      LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
    } else {
      // 扫描到的类在这里做进一步的设置
      processBeanDefinitions(beanDefinitions);
    }
	// 返回设置好的mapper
    return beanDefinitions;
  }

这里是spring的内容, 粗略看一看

	protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
		Assert.notEmpty(basePackages, "At least one base package must be specified");
		// 先new一个结果Set
		Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
		// 对传入的packages进行遍历
		for (String basePackage : basePackages) {
			// findCandidateComponents这个方法就是扫描我们配置的mapper包, 获得class
			// 里面将com.nit.lab.mapper转换为classpath*:com/nit/lab/mapper/**/*.class
			Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
			// 对获取来的class进行遍历
			for (BeanDefinition candidate : candidates) {
				ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
				// 设置candidate的作用域, 这里的scopeName是singleton
				candidate.setScope(scopeMetadata.getScopeName());
				// 获取class的名称
				String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
				// 对这个实例进行更多的设置
				if (candidate instanceof AbstractBeanDefinition) {
					postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
				}
				if (candidate instanceof AnnotatedBeanDefinition) {
					AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
				}
				// 检查这个类名是否需要定义或者是否冲突
				if (checkCandidate(beanName, candidate)) {
					BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
					definitionHolder =
							AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
					beanDefinitions.add(definitionHolder);
					registerBeanDefinition(definitionHolder, this.registry);
				}
			}
		}
		return beanDefinitions;
	}

上面把mapper类扫描完毕后需要加入mybatis的Configuration中
可以得出一个结论, 一个mapper接口会通过MapperFactoryBean加载进Configuration

  public <T> void addMapper(Class<T> type) {
  	// 判断传入的class是否为接口, 所以我们的mapper必须是接口, 如果是类也不报错, 但是不加载
    if (type.isInterface()) {
      // 这里判断这个接口是否已经注册过, 所以返回这是一个已知的mapper
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
      	// 将mapper注入knownMappers, 这是一个hashMap用于存储已经注册过的mapper
        knownMappers.put(type, new MapperProxyFactory<>(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);
        // 进入parse关键步骤
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

parse解析方法

  public void parse() {
  	// 获取type的名称
    String resource = type.toString();
    // 判断Configuration中是否已经加载过这个类名
    if (!configuration.isResourceLoaded(resource)) {
      loadXmlResource();
      // loadedResources中加入这个类
      configuration.addLoadedResource(resource);
      // 设置assistant的当前命名空间为type的name
      assistant.setCurrentNamespace(type.getName());
      // 这里判断了是否有CacheNamespace, 也就是是否开启二级缓存
      // 我们没开二级缓存, 不需要管这里
      parseCache();
      // 同上
      parseCacheRef();
      // 获取mapper中的方法
      Method[] methods = type.getMethods();
      for (Method method : methods) {
        try {
          // issue #237
          if (!method.isBridge()) {
          	// 这里开始解析statement
            parseStatement(method);
          }
        } catch (IncompleteElementException e) {
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }
    parsePendingMethods();
  }

解析statement

void parseStatement(Method method) {
	// 这里获取方法的参数类型, 如果是多个参数就返回数组
    Class<?> parameterTypeClass = getParameterType(method);
    LanguageDriver languageDriver = getLanguageDriver(method);
    // 获取sql语句, 我这边使用注解写的sql, 所以这里获得的就是注解中的value
    SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
    if (sqlSource != null) {
      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 = null;
      // 这里获取该方法的sql类型(SELECT之类的)
      SqlCommandType sqlCommandType = getSqlCommandType(method);
      boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
      // 如果不是select就更新缓存
      boolean flushCache = !isSelect;
      // 如果是select就使用缓存
      boolean useCache = isSelect;

      KeyGenerator keyGenerator;
      String keyProperty = null;
      String keyColumn = null;
      if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
        // first check for SelectKey annotation - that overrides everything else
        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.INSTANCE;
      }
	  // mybatis的@Options注解能够设置缓存时间,能够为对象生成自增的key
      if (options != null) {
        if (FlushCachePolicy.TRUE.equals(options.flushCache())) {
          flushCache = true;
        } else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {
          flushCache = false;
        }
        useCache = options.useCache();
        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 resultMapAnnotation = method.getAnnotation(ResultMap.class);
      // 我这边使用注解sql的时候并没有使用到ResultMap
      if (resultMapAnnotation != null) {
        resultMapId = String.join(",", resultMapAnnotation.value());
      } else if (isSelect) {
        resultMapId = parseResultMap(method);
      }
	  // 这里设置了assistant的returnType, 我这边是用自定义的DTO接收的
      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);
    }
  }

后记

至此Mybatis的mapper加载流程结束了, 总结一下就是先找到mapper接口, 然后加载注册, 然后扫描mapper接口中的方法, 获取sql, 根据sql的类型来做不同的设置, 并且在assistant中注入一个mapperStatement, 这个statemen中有mapper的各种信息, 后面直接用这个创建Statement对象
因为Mybatis其实就是在JDBC的基础上封装的, 所以流程都基本一致

猜你喜欢

转载自blog.csdn.net/Cia_zibo/article/details/106836781
今日推荐