Mybatis源码学习之SqlSessionFactory初始化(一)

957 阅读4分钟

无论是使用xml配置,注解配置,还是boot模式,我们在配置时只需指定mybatis-config配置和Mapper XML文件的路径,然后接口像引用Spring中其他的bean一样使用Mapper接口对数据库进行操作。原因当然是因为在容器初始化时,将Mapper XML文件的配置解析并注入到Spring容器中供我们需要时使用,在下面会对这部分内容进行适当剖析以了解Spring和Mybtais是如何为我们提供便利的。

Mybatis与Spring搭配使用是最常见的使用模式,我们只需要引入mybatis-spring依赖即可。

一、Mybatis原生初始化

首先,看一下Mybatis不依赖Spring的情况下是怎么使用的,参考Mybait官方文档上的例子:

// 不使用XML构建SqlsessionFactory
DataSource dataSource = BlogDataSourceFactory.getBlogDataSource();
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
configuration.addMapper(BlogMapper.class);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
 
// 获取SqlSession
try (SqlSession session = sqlSessionFactory.openSession()) {
  BlogMapper mapper = session.getMapper(BlogMapper.class);
  Blog blog = mapper.selectBlog(101);
}

第一步是构建SqlSessionFactory,可以通过XML构建,比不使用XML构建多了解析XML文件的步骤。(参见mybatis.org/mybatis-3/z…

关键的流程通过上面这个demo可以看得很清楚,初始化的最终目的是构成一个SqlSessionFactory实例,实例中包含Configuration对象,Configurarion包含了所有配置的信息,包括数据源对象DataSource。

具体的初始化工作在SqlSessionFactoryBuilder中完成,如下:

public class SqlSessionFactoryBuilder {
 
    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        try {
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            return build(parser.parse());
        } catch (Exception e) {
            throw ExceptionFactory.wrapException("Error building SqlSession.", e);
        } finally {
            ErrorContext.instance().reset();
        try {
             inputStream.close();
        } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
        }
    }
  }
 
  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }
 
}
 
public class XMLConfigBuilder extends BaseBuilder {
    public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    // 解析配置文件mybatis-config.xml,将xml配置中的内容保存在Configuration对象中
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }
 
private void parseConfiguration(XNode root) {
    try {
      // ...依次解析配置文件中的各类
       
      //   解析Mapper XML配置
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }
 
private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        // 扫描包
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
            // 添加Mapper接口到配置中
            configuration.addMappers(mapperPackage);
        } else {
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            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) {
            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.");
          }
        }
      }
    }
  }
}

可以看到最终也是通过Configutration.addMappers来添加Mapper接口来完成Mapper的接口的初始化。

二、mybatis-spring初始化

通过spring初始化SqlSessionFactory的过程在SqlSesssionFactoryBean中,下面示例一个以代码方式(通过xml配置加载也行)创建SqlSessionFactory的例子:

@Configuration
public class MyBatisConfig {
  @Bean
  public SqlSessionFactory sqlSessionFactory() throws Exception {
    SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
    // 设置DataSource,此处暂时不展开
    factoryBean.setDataSource(dataSource());
 
    PathMatchingResourcePatternResolver pmrpr = new PathMatchingResourcePatternResolver();
     
 
    Resource configLocation = pmrpr.getResource("classpath:META-INF/mybatis/mybatis-config.xml");
    factoryBean.setConfigLocation(configLocation); 
 
    Resource[] configResource = pmrpr.getResources("classpath*:META-INF/mybatis/mapper/*.xml");
    factoryBean.setMapperLocations(configResource);
 
    return factoryBean.getObject();
  }
}

在创建SqlSessionFactory时,最重要的是要设置一个DataSource。当然还要设置mybait配置文件mybatis-config.xml,以及Mapper XML文件路径。

具体初始化过程如下:

protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
 
    Configuration configuration;
 
    XMLConfigBuilder xmlConfigBuilder = null;
    if (this.configuration != null) {
      configuration = this.configuration;
      if (configuration.getVariables() == null) {
        configuration.setVariables(this.configurationProperties);
      } else if (this.configurationProperties != null) {
        configuration.getVariables().putAll(this.configurationProperties);
      }
    } else if (this.configLocation != null) {
      // 初始化解析mybatis配置文件的XMLConfigBuilder
      xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
      configuration = xmlConfigBuilder.getConfiguration();
    } else {
      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
      }
      configuration = new Configuration();
      if (this.configurationProperties != null) {
        configuration.setVariables(this.configurationProperties);
      }
    }
 
    // 省略其他配置的加载
 
    if (xmlConfigBuilder != null) {
      try {
        xmlConfigBuilder.parse();
 
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Parsed configuration file: '" + this.configLocation + "'");
        }
      } catch (Exception ex) {
        throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
      } finally {
        ErrorContext.instance().reset();
      }
    }
 
    // 事务管理Factory
    if (this.transactionFactory == null) {
      this.transactionFactory = new SpringManagedTransactionFactory();
    }
 
    configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
 
    // mapper xml不为空
    if (!isEmpty(this.mapperLocations)) {
      for (Resource mapperLocation : this.mapperLocations) {
        if (mapperLocation == null) {
          continue;
        }
 
        try {
        // 加载解析Mapper XML文件
          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);
  }

整个加载配置的过程跟原生的过程基本上一致,只是通过SqlSesssionFactoryBean作为入口来进行适配。

三、总结

初始化的过程是为了构造一个SqlSessionFactory,用于后续创建SqlSession,而SqlSessionFactory包含的最重要的对象时Configuration,Configuration对象会将所有Mybatis的配置信息进行加载。

以我们最常使用的配置文件XML和Mapper XML文件来说明初始化几个核心的步骤:

1、根据配置mybatis-config.xml的路径和Mapper XML的包路径,加载到Resource中;

2、使用XMLConfigBuiler来解析mybatis-config.xml的配置并设置到Configuration对象中,配置包括mybatis所有配置,包括插件等;

3、使用XMLMapperBuilder来解析Mapper XML的资源,并通过Configuration的addMapper或addMappers来将Mapper接口初始化到容器中;

如果要关注具体哪个配置的细节,比如插件配置等,可以通过查看XMLConfigBuiler的parse过程,如果要关注Mapper XML文件的解析加载过程可以查看XMLMapperBuilder。