mybatis - buildSqlSessionFactory()

buildSqlSessionFactory() 这个方法比较长, 干的事情也比较多. 包括一些别名, 插件, 类型处理器等的解析. 
从主流程上来看, 最主要的其实是干了两件事:
1. 对 mapper.xml 文件进行解析
2. 使用 SqlSessionFactoryBuilder 创建 sqlSessionFactory

mapper.xml的扫描工作不在这个方法里, 但是放到这里来看, 会更加清晰一点.

1. mapperLocation 的解析

  @Bean
  @ConditionalOnMissingBean
  public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
    ......if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
      factory.setMapperLocations(this.properties.resolveMapperLocations());
    }

    return factory.getObject();
  }

在 SqlSessionFactory 的创建方法中, 执行了一个  this.properties.resolveMapperLocations() 方法.

  public Resource[] resolveMapperLocations() {
    ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
    List<Resource> resources = new ArrayList<Resource>();
    if (this.mapperLocations != null) {
      for (String mapperLocation : this.mapperLocations) {
        try {
          Resource[] mappers = resourceResolver.getResources(mapperLocation);
          resources.addAll(Arrays.asList(mappers));
        } catch (IOException e) {
          // ignore
        }
      }
    }
    return resources.toArray(new Resource[resources.size()]);
  }
PathMatchingResourcePatternResolver org.springframework.core 里面的一个类, 是 spring 提供的. 
可以对 mapperLocations = classpath:mapper/**Mapper.xml 进行解析, 并拿到匹配路径下的文件资源

此例中得到的结果是 SchoolMapper.xml 和 UserMapper.xml 文件的 Resource . 并将它们放入了 mapperLocations 属性中.

 

2. mapper.xml 文件的解析

  protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
......
    if (!isEmpty(this.mapperLocations)) {
      for (Resource mapperLocation : this.mapperLocations) {
        if (mapperLocation == null) {
          continue;
        }

        try {
          XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
              configuration, mapperLocation.toString(), configuration.getSqlFragments());
          xmlMapperBuilder.parse();
        } catch (Exception e) {
          throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
        } finally {
          ErrorContext.instance().reset();
        }

        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
        }
      }
    } else {
      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
      }
    }

    return this.sqlSessionFactoryBuilder.build(configuration);
  }

这里的  this.mapperLocations 就是 SchoolMapper.xml 和 UserMapper.xml 的 Resource.

这里主要关注一下 parse() 方法.

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

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

 

2.1  解析mapper.xml配置文件

  private void configurationElement(XNode context) {
    try {
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || 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. The XML location is '" + resource + "'. Cause: " + e, e);
    }
  }

这个方法中, 对xml中的配置进行解析, 并使用  SqlSource  进行记录. 最终封装进 Configuration 的 MappedStatement  mappedStatement 属性中

如例子中的 getById方法:

 这里注意一个方法:

  public void addMappedStatement(MappedStatement ms) {
    mappedStatements.put(ms.getId(), ms);
  }

这里的id是怎么来的呢?

MapperBuilderAssistant.java
  public String applyCurrentNamespace(String base, boolean isReference) {
    if (base == null) {
      return null;
    }
    if (isReference) {
      // is it qualified with any namespace yet?
      if (base.contains(".")) {
        return base;
      }
    } else {
      // is it qualified with this namespace yet?
      if (base.startsWith(currentNamespace + ".")) {
        return base;
      }
      if (base.contains(".")) {
        throw new BuilderException("Dots are not allowed in element names, please remove it from " + base);
      }
    }
    return currentNamespace + "." + base;
  }

一番解析之后, UserMapper.xml 的getById 的 id = "com.study.demo.mybatis.mapper.SchoolMapper.getById"

 

2.2 绑定

  private void bindMapperForNamespace() {
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class<?> boundType = null;
      try {
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
        //ignore, bound type is not required
      }
      if (boundType != null) {
        if (!configuration.hasMapper(boundType)) {
          // 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.addLoadedResource("namespace:" + namespace);
          configuration.addMapper(boundType);
        }
      }
    }
  }

从方法明上看, 此方法将 mapper 和 namespace 进行绑定. 

在addMapper 方法中, 执行了一个重要方法:  getSqlSourceFromAnnotations()

这个方法其实就是对 接口方法进行注解检测的.

此例中, 如果将 SchoolMapper.xml 中的配置删掉, 然后改写SchoolMapper.java 中的getById方法

    @Select("select id, name from school where id = #{id}")
    public School getById(@Param("id") Integer id);

那么就会在这里解析并赋值.

那如果我即配置了 mapper.xml 文件, 又配置了 @Select 注解的情况下, 到底是 mapper.xml 起作用还是 @Select 起作用呢?

1. 当然, 首先不可能都起作用, 这个是很明确的, 不然出现歧义的时候, 是咋个处理呢?

2. 在解析@Select 之前, 其实已经解析过 mapper.xml 了, 如果xml已经解析并且记录, 那么即使 @Select 解析失败, 那是不是起码还有一个可以使用呢?

事实上, 不能那样使用, 会报错的. 且会导致 mapper.xml 也用不上了. 程序启动也会中断, 不能继续进行. 

Caused by: java.lang.IllegalArgumentException: Mapped Statements collection already contains value for com.study.demo.mybatis.mapper.SchoolMapper.getById

 

3. SqlSessionFactory创建

  //  SqlSessionFactoryBuilder.java
  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

这里创建了一个默认的实现类 :  DefaultSqlSessionFactory, 且持有了 Configuration 的引用.

 

到这里, mapper.java(还未创建实例) , mapper.xml 都已经找到了. 

posted @ 2020-02-12 18:10  Sniper_ZL  阅读(856)  评论(0编辑  收藏  举报