• mybatis


    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 都已经找到了. 

  • 相关阅读:
    git更新代码
    git标签
    git分支
    命令连接redis
    sql语句
    rm -rf无法删除文件解决方法
    lombda 使用记录
    centos查看磁盘空间大小
    CentOS7 防火墙Firewall常用命令
    安装rabbitmq
  • 原文地址:https://www.cnblogs.com/elvinle/p/12297324.html
Copyright © 2020-2023  润新知