【Spring源码】SpringBoot_MyBatis_starter源码(一)

作者: Weidan 分类: Spring源码 发布时间: 2020-01-12

准备一下

由于我们使用 MyBatis 的时候,很少独立使用,所以我直接从 SpringBoot 自动装配,进入 MyBatis 来看看。

直接看 org.mybatis.spring.boot:mybatis-spring-boot-starter 源码目录结构

├── LICENSE
├── README.md
├── license.txt
├── mvnw
├── mvnw.cmd
├── mybatis-spring-boot-autoconfigure
│   ├── format.xml
│   ├── license.txt
│   ├── pom.xml
│   └── src
├── mybatis-spring-boot-samples
│   ├── mybatis-spring-boot-sample-annotation
│   ├── mybatis-spring-boot-sample-freemarker
│   ├── mybatis-spring-boot-sample-freemarker-legacy
│   ├── mybatis-spring-boot-sample-groovy
│   ├── mybatis-spring-boot-sample-kotlin
│   ├── mybatis-spring-boot-sample-thymeleaf
│   ├── mybatis-spring-boot-sample-velocity
│   ├── mybatis-spring-boot-sample-velocity-legacy
│   ├── mybatis-spring-boot-sample-war
│   ├── mybatis-spring-boot-sample-web
│   ├── mybatis-spring-boot-sample-xml
│   └── pom.xml
├── mybatis-spring-boot-starter
│   ├── license.txt
│   └── pom.xml
├── mybatis-spring-boot-starter-test
│   └── pom.xml
├── mybatis-spring-boot-test-autoconfigure
│   ├── format.xml
│   ├── pom.xml
│   └── src
├── pom.xml
└── travis
    ├── after_success.sh
    └── settings.xml

mybatis-spring-boot-samples/mybatis-spring-boot-sample-xml 中刚好有我想要的示例,刚好不用自己写了,就使用这个示例来查看吧。

自动发现

从之前的自动发现文章中,我们发现,通常自动装配的类都写在 META-INF/spring.factories 这里面,现在我们可以直接查看 mybatis-spring-boot-autoconfigure 这里面的这个文件:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

通过名字,感觉 org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration 在装载的过程中比较重要。

这个类里面有个内部类,是 MyBatis 切入 SpringBoot 的重点:

public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar {

  private BeanFactory beanFactory;

  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

    if (!AutoConfigurationPackages.has(this.beanFactory)) {
      logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.");
      return;
    }

    logger.debug("Searching for mappers annotated with @Mapper");

    List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
    if (logger.isDebugEnabled()) {
      packages.forEach(pkg -> logger.debug("Using auto-configuration base package '{}'", pkg));
    }

    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
    builder.addPropertyValue("processPropertyPlaceHolders", true);
    builder.addPropertyValue("annotationClass", Mapper.class);
    builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));
    BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class);
    Stream.of(beanWrapper.getPropertyDescriptors())
        // Need to mybatis-spring 2.0.2+
        .filter(x -> x.getName().equals("lazyInitialization")).findAny()
        .ifPresent(x -> builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}"));
    registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
  }

  @Override
  public void setBeanFactory(BeanFactory beanFactory) {
    this.beanFactory = beanFactory;
  }

}

@org.springframework.context.annotation.Configuration
@Import(AutoConfiguredMapperScannerRegistrar.class)
@ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class })
public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {

    @Override
    public void afterPropertiesSet() {
      logger.debug(
        "Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer.");
  }

}

这个类实现了两个接口:

  1. BeanFactoryAware:这个接口,被 Spring 扫描到的话,是会被 Spring 调用的,传递了当前 BeanFactory 实例,有助于我们后期可以对 BeanFactory 的操作;
  2. ImportBeanDefinitionRegistrar:那这个接口还真的是第一次出现,他是一个在解析 Configuration,可以注册业务需要的 BeanDefinition,简单的说,在刷新容器的时候,BeanDefinitionRegistryPostProcessors 会被调用到,而 SpringBoot 中,总是有个 ConfigurationClassPostProcessor 存在,他在解析配置的时候,会手动的去触发 ImportBeanDefinitionRegistrar#registerBeanDefinitions 函数,这时候,上面的内部类就会被读取到。

那这个类他就是负责在解析配置类的时候,注册了一个 MapperScannerConfigurer 类,看名字不用猜就知道是扫描项目中被 @Mapper 注解装饰的接口类了。

配置 SqlSession

上面注册的 MapperScannerConfigurer 是一个 BeanDefinitionRegistryPostProcessor 处理器,他会在解析配置后面,被 BeanFactory 调用到。

调用就是扫描 Mapper 的存在了。

public class MapperScannerConfigurer
    implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
    // ...
  @Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }

    // 创建扫描器
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    scanner.setAddToConfig(this.addToConfig);
    scanner.setAnnotationClass(this.annotationClass);
    scanner.setMarkerInterface(this.markerInterface);
    // 目前SqlSessionFactory和SqlSessionTemplate均为Null
    scanner.setSqlSessionFactory(this.sqlSessionFactory);
    scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
    scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
    scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
    scanner.setResourceLoader(this.applicationContext);
    scanner.setBeanNameGenerator(this.nameGenerator);
    scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
    if (StringUtils.hasText(lazyInitialization)) {
      scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
    }
    // 注册扫描过滤器,目前是加上扫描 @Mapper 的接口类
    scanner.registerFilters();
    // 开始扫描
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }
  // ...
}
public int scan(String... basePackages) {
 int beanCountAtScanStart = this.registry.getBeanDefinitionCount();

 doScan(basePackages);

 // Register annotation config processors, if necessary.
 if (this.includeAnnotationConfig) {
  // 一个空处理,目的是为了注册注解配置类吧?
  AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
 }

 return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}

由于 ClassPathMapperScanner 继承了 Spring 提供的 ClassPathBeanDefinitionScanner,所以可以直接把扫描注解这件事情交给 Spring 来做,

@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
  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);
  }

  return beanDefinitions;
}

那由于上面已经扫描了大半的 BeanDefinition,后面就新增了还需要扫描 @Mapper 的注解,这时候重新进行一遍扫描以后,新增加的 BeanDefinition 就是我们要的 Mapper 接口了。所以这时候拿到了所有的 MapperBeanDefinition 进行处理。

接下来对扫描到 Mapper 进行处理一波:

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
  GenericBeanDefinition definition;
  for (BeanDefinitionHolder holder : beanDefinitions) {
    definition = (GenericBeanDefinition) holder.getBeanDefinition();
    String beanClassName = definition.getBeanClassName();
    LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
        + "' mapperInterface");

    // Bean是接口,但是其实现类是 MapperFactoryBean,所以这里设置 mapperFactoryBean 进去所有的 Mapper里面
    definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
    definition.setBeanClass(this.mapperFactoryBeanClass);

    definition.getPropertyValues().add("addToConfig", this.addToConfig);

    boolean explicitFactoryUsed = false;
    // 这下面目前均为空,所以都没有进入if里面
    if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
      definition.getPropertyValues().add("sqlSessionFactory",
          new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
      explicitFactoryUsed = true;
    } else if (this.sqlSessionFactory != null) {
      definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
      explicitFactoryUsed = true;
    }

    if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
      if (explicitFactoryUsed) {
        LOGGER.warn(
            () -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
      }
      definition.getPropertyValues().add("sqlSessionTemplate",
          new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
      explicitFactoryUsed = true;
    } else if (this.sqlSessionTemplate != null) {
      if (explicitFactoryUsed) {
        LOGGER.warn(
            () -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
      }
      definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
      explicitFactoryUsed = true;
    }

    // 根据类型注入
    if (!explicitFactoryUsed) {
      LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
      definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
    }
    definition.setLazyInit(lazyInitialization);
  }
}

获取Mapper

实例化在 ApplicationContext#finishBeanFactoryInitialization 中这一步实现 finishBeanFactoryInitialization。这时候需要先看下 MyBatis 自动配置配置了哪些 Bean

public class MybatisAutoConfiguration implements InitializingBean {

  @Bean
  @ConditionalOnMissingBean
  public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
    factory.setDataSource(dataSource);
    factory.setVfs(SpringBootVFS.class);
    if (StringUtils.hasText(this.properties.getConfigLocation())) {
      factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
    }
    applyConfiguration(factory);
    if (this.properties.getConfigurationProperties() != null) {
      factory.setConfigurationProperties(this.properties.getConfigurationProperties());
    }
    if (!ObjectUtils.isEmpty(this.interceptors)) {
      factory.setPlugins(this.interceptors);
    }
    if (this.databaseIdProvider != null) {
      factory.setDatabaseIdProvider(this.databaseIdProvider);
    }
    if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
      factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
    }
    if (this.properties.getTypeAliasesSuperType() != null) {
      factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType());
    }
    if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
      factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
    }
    if (!ObjectUtils.isEmpty(this.typeHandlers)) {
      factory.setTypeHandlers(this.typeHandlers);
    }
    if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
      factory.setMapperLocations(this.properties.resolveMapperLocations());
    }
    Set<String> factoryPropertyNames = Stream
        .of(new BeanWrapperImpl(SqlSessionFactoryBean.class).getPropertyDescriptors()).map(PropertyDescriptor::getName)
        .collect(Collectors.toSet());
    Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();
    if (factoryPropertyNames.contains("scriptingLanguageDrivers") && !ObjectUtils.isEmpty(this.languageDrivers)) {
      // Need to mybatis-spring 2.0.2+
      factory.setScriptingLanguageDrivers(this.languageDrivers);
      if (defaultLanguageDriver == null && this.languageDrivers.length == 1) {
        defaultLanguageDriver = this.languageDrivers[0].getClass();
      }
    }
    if (factoryPropertyNames.contains("defaultScriptingLanguageDriver")) {
      // Need to mybatis-spring 2.0.2+
      factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver);
    }

    return factory.getObject();
  }

  @Bean
  @ConditionalOnMissingBean
  public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
    ExecutorType executorType = this.properties.getExecutorType();
    if (executorType != null) {
      return new SqlSessionTemplate(sqlSessionFactory, executorType);
    } else {
      return new SqlSessionTemplate(sqlSessionFactory);
    }
  }

}

SqlSessionFactorySqlSessionTemplate,先来看看这两个类的作用:

这两个类主要围绕构建 SqlSession 来做一些配置的。

public interface SqlSession extends Closeable {

  <T> T selectOne(String statement);

  <T> T selectOne(String statement, Object parameter);

  <E> List<E> selectList(String statement);

  <E> List<E> selectList(String statement, Object parameter);

  <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);

  <K, V> Map<K, V> selectMap(String statement, String mapKey);

  <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey);

  <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);

  <T> Cursor<T> selectCursor(String statement);

  <T> Cursor<T> selectCursor(String statement, Object parameter);

  <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds);

  void select(String statement, Object parameter, ResultHandler handler);

  void select(String statement, ResultHandler handler);

  void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);

  int insert(String statement);

  int insert(String statement, Object parameter);

  int update(String statement);

  int update(String statement, Object parameter);

  int delete(String statement);

  int delete(String statement, Object parameter);

  void commit();

  void commit(boolean force);

  void rollback();

  void rollback(boolean force);

  List<BatchResult> flushStatements();

  @Override
  void close();

  void clearCache();

  Configuration getConfiguration();

  <T> T getMapper(Class<T> type);

  Connection getConnection();
}

这是 MyBatis 执行 SQL 的关键接口,定义了 jdbc 常用的方法。而 SqlSessionTemplate 就是一个 SqlSession,专门用来整合 Spring 的一个 SqlSession,而 SqlSessionFactory 则是封装了连接的方法以及一些其他的配置,比如拦截器等等。

那我们看看主启动类:

@SpringBootApplication
public class SampleXmlApplication implements CommandLineRunner {

  public static void main(String[] args) {
    SpringApplication.run(SampleXmlApplication.class, args);
  }

  private final CityDao cityDao;

  private final HotelMapper hotelMapper;

  public SampleXmlApplication(CityDao cityDao, HotelMapper hotelMapper) {
    this.cityDao = cityDao;
    this.hotelMapper = hotelMapper;
  }

  @Override
  @SuppressWarnings("squid:S106")
  public void run(String... args) {
    System.out.println(this.cityDao.selectCityById(1));
    System.out.println(this.hotelMapper.selectByCityId(1));
  }

}

他注入了一个 CityDao 以及 一个 HotelMapper,两种不同的查询方式。

@Component
public class CityDao {

  private final SqlSession sqlSession;

  public CityDao(SqlSession sqlSession) {
    this.sqlSession = sqlSession;
  }

  public City selectCityById(long id) {
    return this.sqlSession.selectOne("selectCityById", id);
  }

}

@Mapper
public interface HotelMapper {

  Hotel selectByCityId(int cityId);

}

那么,Spring 容器在初始化 SampleXmlApplication 的时候就会去循环递归的去找到最后所需要的依赖,那其实 CityDao 的初始化并不难,现在看看 HotelMapper 的初始化。

HotelMapper 在注册给 Spring 的时候其实已经将实际对象的 FactoryBean 设置成 MapperFactoryBean,而 MapperFactoryBean 实现了 FactoryBeanSpring 将会通过调用 getObject 来获取工厂构造的对象,所以这时候我们看看 MapperFactoryBean 怎么实现这个方法的:

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {

  // 我们自己写的Mapper接口类
  private Class<T> mapperInterface;

  private boolean addToConfig = true;

  public MapperFactoryBean() {
    // intentionally empty
  }

  public MapperFactoryBean(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  // FactoryBean需要实现这个方法
  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }

}

OK,我们可以看到他从 SqlSession 中来获取 Mapper代理,所以这个方法在这里充其量就是一个桥梁,架通了 MyBatis 构造的代理以及 SpringBean 的桥。

SqlSession 怎么来的,又是一个 Factory 构造来的,还记得上面的工厂配置类吗,就是那里构造出来的,现在我们需要回去去看看他怎么构造 MyBatis 上下文的:

  @Bean
  @ConditionalOnMissingBean
  public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    // 构造一个SqlSessionFactory
    SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
    // 数据源
    factory.setDataSource(dataSource);
    // 提供一个访问SpringBoot资源的实现类,是个VFS,MyBatis定义访问资源的一个抽象类
    factory.setVfs(SpringBootVFS.class);
    // MyBatis配置文件:mybatis.config-location=classpath:mybatis-config.xml
    // 在applicationContext.yml中的配置
    if (StringUtils.hasText(this.properties.getConfigLocation())) {
      factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
    }
    // 读取MyBatisProperties配置,此时SpringBoot已经将配置文件中的信息转换为JavaBean
    // 不过我们没有配置什么东西
    applyConfiguration(factory);
    if (this.properties.getConfigurationProperties() != null) {
      factory.setConfigurationProperties(this.properties.getConfigurationProperties());
    }
    // MyBatis拦截器
    if (!ObjectUtils.isEmpty(this.interceptors)) {
      factory.setPlugins(this.interceptors);
    }
    // 主键Id生成器
    if (this.databaseIdProvider != null) {
      factory.setDatabaseIdProvider(this.databaseIdProvider);
    }
    // 接收数据JavaBean所在的包
    if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
      factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
    }
    // 接收数据JavaBean父类
    if (this.properties.getTypeAliasesSuperType() != null) {
      factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType());
    }
    // 类型处理器所在的包
    if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
      factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
    }
    // 手动指定的类型处理器
    if (!ObjectUtils.isEmpty(this.typeHandlers)) {
      factory.setTypeHandlers(this.typeHandlers);
    }
    // Mapper.xml所在的位置
    if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
      factory.setMapperLocations(this.properties.resolveMapperLocations());
    }
    // 判断使用的自动SQL脚本驱动,看到这里我才知道SQL模板不仅支持xml,还支持FreeMarker/thymeleaf等模板引擎
    Set<String> factoryPropertyNames = Stream
        .of(new BeanWrapperImpl(SqlSessionFactoryBean.class).getPropertyDescriptors()).map(PropertyDescriptor::getName)
        .collect(Collectors.toSet());
    Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();
    if (factoryPropertyNames.contains("scriptingLanguageDrivers") && !ObjectUtils.isEmpty(this.languageDrivers)) {
      // Need to mybatis-spring 2.0.2+
      factory.setScriptingLanguageDrivers(this.languageDrivers);
      if (defaultLanguageDriver == null && this.languageDrivers.length == 1) {
        defaultLanguageDriver = this.languageDrivers[0].getClass();
      }
    }
    if (factoryPropertyNames.contains("defaultScriptingLanguageDriver")) {
      // Need to mybatis-spring 2.0.2+
      factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver);
    }

    // 构建SqlSessionFactory,在这里做了MyBatis上下文的初始化。
    return factory.getObject();
  }

来到这里,终于进来了 MyBatis 的内容了,就是开始读取配置,解析,然后这个 Configuration 类将贯穿相应使用 MyBatis 的整个生命周期。

// SqlSessionFactoryBean.Java
@Override
public SqlSessionFactory getObject() throws Exception {
  if (this.sqlSessionFactory == null) {
    afterPropertiesSet();
  }

  return this.sqlSessionFactory;
}
@Override
public void afterPropertiesSet() throws Exception {
  // 校验连接池是否存在
  notNull(dataSource, "Property 'dataSource' is required");
  notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
  // 'configuration' and 'configLocation'不能同时声明
  state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
        "Property 'configuration' and 'configLocation' can not specified with together");

  // 开始构建
  this.sqlSessionFactory = buildSqlSessionFactory();
}

目前官方示例给的一个配置就是简单的扫描接收类型的包以及 Mapper 映射的路径:

<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <typeAliases>
        <package name="sample.mybatis.domain"/>
    </typeAliases>
    <mappers>
        <mapper resource="sample/mybatis/mapper/CityMapper.xml"/>
        <mapper resource="sample/mybatis/mapper/HotelMapper.xml"/>
    </mappers>
</configuration>

进入 buildSqlSessionFactory 我们就可以看到配置熟悉的东西都在这里被解析,这个代码没点 j8 基础都看不懂了:

// SqlSessionFactoryBean.Java
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {

  final Configuration targetConfiguration;

  // Builder模式
  XMLConfigBuilder xmlConfigBuilder = null;
  // 如果我们配置了 configuration 对象,这里可以减少xml的读取,有效提速吧
  if (this.configuration != null) {
    targetConfiguration = this.configuration;
    if (targetConfiguration.getVariables() == null) {
      targetConfiguration.setVariables(this.configurationProperties);
    } else if (this.configurationProperties != null) {
      targetConfiguration.getVariables().putAll(this.configurationProperties);
    }
  } else if (this.configLocation != null) {
    // 构建XMLConfigBuilder,与下面的SpringBoot配置一起解析
    xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
    // 还没解析,只是先把默认的 Configuration 引用拿到
    targetConfiguration = xmlConfigBuilder.getConfiguration();
  } else {
    // 没有配置,使用默认的配置
    LOGGER.debug(
        () -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
    targetConfiguration = new Configuration();
    Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
  }

  // 对象工厂
  Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
  // 对象包装工厂  
 Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
  // SpringBoot扫描的资源了
  Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);

  // ------------- 下面是通过SpringBoot自动配置的方式去整合xml的配置了 -------------
  if (hasLength(this.typeAliasesPackage)) {
    scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream()
        .filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface())
        .filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
  }

  if (!isEmpty(this.typeAliases)) {
    Stream.of(this.typeAliases).forEach(typeAlias -> {
      targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
      LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
    });
  }

  if (!isEmpty(this.plugins)) {
    Stream.of(this.plugins).forEach(plugin -> {
      targetConfiguration.addInterceptor(plugin);
      LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
    });
  }

  if (hasLength(this.typeHandlersPackage)) {
    scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass())
        .filter(clazz -> !clazz.isInterface()).filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))
        .forEach(targetConfiguration.getTypeHandlerRegistry()::register);
  }

  if (!isEmpty(this.typeHandlers)) {
    Stream.of(this.typeHandlers).forEach(typeHandler -> {
      targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
      LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
    });
  }

  if (!isEmpty(this.scriptingLanguageDrivers)) {
    Stream.of(this.scriptingLanguageDrivers).forEach(languageDriver -> {
      targetConfiguration.getLanguageRegistry().register(languageDriver);
      LOGGER.debug(() -> "Registered scripting language driver: '" + languageDriver + "'");
    });
  }
  Optional.ofNullable(this.defaultScriptingLanguageDriver)
      .ifPresent(targetConfiguration::setDefaultScriptingLanguage);

  if (this.databaseIdProvider != null) {// fix #64 set databaseId before parse mapper xmls
    try {
      targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
    } catch (SQLException e) {
      throw new NestedIOException("Failed getting a databaseId", e);
    }
  }

  Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);

  if (xmlConfigBuilder != null) {
    try {
      // 解析xml,将配置应用到Configuration中
      xmlConfigBuilder.parse();
      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();
    }
  }

  targetConfiguration.setEnvironment(new Environment(this.environment,
      this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
      this.dataSource));

  if (this.mapperLocations != null) {
    if (this.mapperLocations.length == 0) {
      LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
    } else {
      for (Resource mapperLocation : this.mapperLocations) {
        if (mapperLocation == null) {
          continue;
        }
        try {
          XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
              targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
          xmlMapperBuilder.parse();
        } catch (Exception e) {
          throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
        } finally {
          ErrorContext.instance().reset();
        }
        LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
      }
    }
  } else {
    LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
  }

  // 方法就是简单的将配置放入 DefaultSqlSessionFactory 中返回出去
  // 用于在项目中需要开启SqlSession的时候可以用到
  return this.sqlSessionFactoryBuilder.build(targetConfiguration);

  /*
  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }
  */


}

解析Configuration.xml

// XMLConfigBuilder.java
public Configuration parse() {
  if (parsed) {
    throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  }
  parsed = true;
  parseConfiguration(parser.evalNode("/configuration"));
  return configuration;
}
// XMLConfigBuilder.java
private void parseConfiguration(XNode root) {
  try {
    // 解析属性元素
    propertiesElement(root.evalNode("properties"));
    Properties settings = settingsAsProperties(root.evalNode("settings"));
    loadCustomVfs(settings);
    loadCustomLogImpl(settings);
    typeAliasesElement(root.evalNode("typeAliases"));
    pluginElement(root.evalNode("plugins"));
    objectFactoryElement(root.evalNode("objectFactory"));
    objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    reflectorFactoryElement(root.evalNode("reflectorFactory"));
    settingsElement(settings);
    // read it after objectFactory and objectWrapperFactory issue #631
    environmentsElement(root.evalNode("environments"));
    databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    typeHandlerElement(root.evalNode("typeHandlers"));
    mapperElement(root.evalNode("mappers"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  }
}

这里大概就是 Configuration.xml 的所有内容的解析,那我们就直接看看解析 SqlMapper 的方法。

// <mapper resource="sample/mybatis/mapper/HotelMapper.xml"/>
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");
        configuration.addMappers(mapperPackage);
      } else {
        // 获取 resource
        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.");
        }
      }
    }
  }
}

解析Mapper.xml

// XMLMapperBuilder.java
public void parse() {
  if (!configuration.isResourceLoaded(resource)) {
    // 解析解析所有标签
    configurationElement(parser.evalNode("/mapper"));
    // 标记已经解析完成
    configuration.addLoadedResource(resource);
    // 注册接口和xml绑定的类型
    bindMapperForNamespace();
  }

  // 解析ResultMaps
  parsePendingResultMaps();
  // 解析缓存
  parsePendingCacheRefs();
  // 解析SQL语句
  parsePendingStatements();
}

解析Statement语句

主要是解析标签比较重要,我们现在进入来看看 configurationElement(parser.evalNode("/mapper")) 方法:

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);
  }
}

结合 Mapper.xml 来看看:

<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sample.mybatis.mapper.HotelMapper">
    <select id="selectByCityId" resultType="Hotel">
        select city, name, address, zip from hotel where city = #{id}
    </select>
</mapper>

解析语句是发生在 buildStatementFromContext 里面的:

private void buildStatementFromContext(List<XNode> list) {
  // 根据不同的数据库id来绑定语句
  if (configuration.getDatabaseId() != null) {
    buildStatementFromContext(list, configuration.getDatabaseId());
  }
  buildStatementFromContext(list, null);
}

不得不说,MyBatis 还真的是喜欢 Builder

private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
  // 循环每一个 statement 标签,开始解析
  for (XNode context : list) {
    final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
    try {
      // 发生解析,将 mapper.xml 的信息存入 Configuration
      statementParser.parseStatementNode();
    } catch (IncompleteElementException e) {
      configuration.addIncompleteStatement(statementParser);
    }
  }
}
public void parseStatementNode() {
  String id = context.getStringAttribute("id");
  String databaseId = context.getStringAttribute("databaseId");

  if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
    return;
  }

  String nodeName = context.getNode().getNodeName();
  SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
  boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
  boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
  boolean useCache = context.getBooleanAttribute("useCache", isSelect);
  boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

  // Include Fragments before parsing
  XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
  includeParser.applyIncludes(context.getNode());

  String parameterType = context.getStringAttribute("parameterType");
  Class<?> parameterTypeClass = resolveClass(parameterType);

  String lang = context.getStringAttribute("lang");
  LanguageDriver langDriver = getLanguageDriver(lang);

  // Parse selectKey after includes and remove them.
  processSelectKeyNodes(id, parameterTypeClass, langDriver);

  // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
  KeyGenerator keyGenerator;
  String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
  keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
  if (configuration.hasKeyGenerator(keyStatementId)) {
    keyGenerator = configuration.getKeyGenerator(keyStatementId);
  } else {
    keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
        configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
        ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
  }

  // 构建SQLSource对象,这个接口将动态SQL和静态的划分成两个不同的对象
  SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
  // 获取判断是否是预编译的SQL语句
  StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
  Integer fetchSize = context.getIntAttribute("fetchSize");
  Integer timeout = context.getIntAttribute("timeout");
  String parameterMap = context.getStringAttribute("parameterMap");
  // 获取返回的对象类型
  String resultType = context.getStringAttribute("resultType");
  // 结合配置文件中的typeAliases来获取到Class对象
  Class<?> resultTypeClass = resolveClass(resultType);
  String resultMap = context.getStringAttribute("resultMap");
  String resultSetType = context.getStringAttribute("resultSetType");
  ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
  if (resultSetTypeEnum == null) {
    resultSetTypeEnum = configuration.getDefaultResultSetType();
  }
  String keyProperty = context.getStringAttribute("keyProperty");
  String keyColumn = context.getStringAttribute("keyColumn");
  String resultSets = context.getStringAttribute("resultSets");

  // 通过builderAssistant将Mapper对象塞入Configuration
  builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
      fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
      resultSetTypeEnum, flushCache, useCache, resultOrdered,
      keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

通过构建好的配置信息,将查询的配置加入 Configuration

// builderAssistant.addMappedStatement
public MappedStatement addMappedStatement(
    String id,
    SqlSource sqlSource,
    StatementType statementType,
    SqlCommandType sqlCommandType,
    Integer fetchSize,
    Integer timeout,
    String parameterMap,
    Class<?> parameterType,
    String resultMap,
    Class<?> resultType,
    ResultSetType resultSetType,
    boolean flushCache,
    boolean useCache,
    boolean resultOrdered,
    KeyGenerator keyGenerator,
    String keyProperty,
    String keyColumn,
    String databaseId,
    LanguageDriver lang,
    String resultSets) {

  if (unresolvedCacheRef) {
    throw new IncompleteElementException("Cache-ref not yet resolved");
  }

  // 获取id,sample.mybatis.mapper.HotelMapper.selectByCityId
  id = applyCurrentNamespace(id, false);
  boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

  MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
      .resource(resource)
      .fetchSize(fetchSize)
      .timeout(timeout)
      .statementType(statementType)
      .keyGenerator(keyGenerator)
      .keyProperty(keyProperty)
      .keyColumn(keyColumn)
      .databaseId(databaseId)
      .lang(lang)
      .resultOrdered(resultOrdered)
      .resultSets(resultSets)
      .resultMaps(getStatementResultMaps(resultMap, resultType, id))
      .resultSetType(resultSetType)
      .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
      .useCache(valueOrDefault(useCache, isSelect))
      .cache(currentCache);

  ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
  if (statementParameterMap != null) {
    statementBuilder.parameterMap(statementParameterMap);
  }

  MappedStatement statement = statementBuilder.build();
  // 存入configuration
  configuration.addMappedStatement(statement);
  return statement;
}

注册Mapper到MyBatis的Registry

Configuration 中,解析 Mapper 已经完成,接下来就是注册一个接口代理对象到 Configuration 中去:

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
        configuration.addMapper(boundType);
      }
    }
  }
}

重点就在于这个 `configuration.addMapper` 中:

public class Configuration {
  // ...
  public <T> void addMapper(Class<T> type) {
    mapperRegistry.addMapper(type);
  }
  // ...
}
public class MapperRegistry {
    // ...
    public <T> void addMapper(Class<T> type) {
        if (type.isInterface()) {
          if (hasMapper(type)) {
            throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
          }
          boolean loadCompleted = false;
          try {
            // 使用 MapperProxyFactory 封装接口类型,加入Registery中
            knownMappers.put(type, new MapperProxyFactory<>(type));
            // 加入后,继续解析这个接口,看看有没有 @Select 之类的注解存在
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
            parser.parse();
            loadCompleted = true;
          } finally {
            if (!loadCompleted) {
              knownMappers.remove(type);
            }
          }
        }
    }
    // ...
}

MapperFactoryBean对接Spring

还记得上面对接 SpringgetBean 的对接桥梁吗,就是 MapperFactoryBean 来适配接口的:

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
  // ...
  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }
  // ...
}

public class SqlSessionTemplate implements SqlSession, DisposableBean {
  // ...
  @Override
  public <T> T getMapper(Class<T> type) {
    return getConfiguration().getMapper(type, this);
  }
  // ...
}

public class Configuration {
  // ...
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }
  // ...
}

public class MapperRegistry {
  // ...
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }
  // ...
}

public class MapperProxyFactory<T> {
  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }
}

到达这里,初始化工作基本已经完成,现在我们要看看运行时怎么获取数据的


获取代理对象

那么现在就回到主程序中,来观察 MyBatis 是怎么封装 Mapper 接口来查询数据的,这里我重新贴一下主程序中的查询入口:

@SpringBootApplication
public class SampleXmlApplication implements CommandLineRunner {

  public static void main(String[] args) {
    SpringApplication.run(SampleXmlApplication.class, args);
  }

  private final CityDao cityDao;

  private final HotelMapper hotelMapper;

  public SampleXmlApplication(CityDao cityDao, HotelMapper hotelMapper) {
    this.cityDao = cityDao;
    this.hotelMapper = hotelMapper;
  }

  @Override
  @SuppressWarnings("squid:S106")
  public void run(String... args) {
    System.out.println(this.cityDao.selectCityById(1));
    // 常用的是这种方式,我直接看这里了
    System.out.println(this.hotelMapper.selectByCityId(1));
  }

}

刚刚上面提到过,MyBatis 在整合 Spring 的时候其实就将自己 Registry 中的 MapperProxy 给放入 BeanDefinition 中,然后交给 Spring 去自动注入到需要依赖的对象。那么现在拿到的对象就是一个 MapperProxy

既然是 MapperProxy,那要看怎么封装查询数据就是看这个了。

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (method.isDefault()) {
        if (privateLookupInMethod == null) {
          return invokeDefaultMethodJava8(proxy, method, args);
        } else {
          return invokeDefaultMethodJava9(proxy, method, args);
        }
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

MapperProxy 是何方神圣,哦,是一个 InvocationHandler,这是一个我从来没见过的 jdk 接口。J8 专有的动态代理的接口,那他有什么用呢,就是将实现封装在这个实现类里边,实现类有个 invoke 方法需要实现,那么实现类的所有方法被调用时,都需要先走这个方法,这时候我们就可以在 invoke 里面做一些羞羞的事情。

InvocationHandler代理

那就来示例一下这个东西怎么用:

// 一个普通的接口
public interface A {
  void add();
}

// 代理处理器
public class InvokeHandler<T> implements InvocationHandler {

  Class<T> type;

  public InvokeHandler(Class<T> tClass) {
    this.type = tClass;
  }

  // 将会拦截接口的方法
  @Override
  public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
    System.out.println("调用到这里来了");
    return null;
  }

  // 创建一个代理对象,接口中方法被调用的时候,均为走上面的invoke方法
  public T getProxy() {
    return (T) Proxy.newProxyInstance(type.getClassLoader(),
        new Class[]{type}, this);
  }

}


public class TestInvoke {

  public static void main(String[] args) {
    A proxy = new InvokeHandler<>(A.class).getProxy();
    proxy.add();// 输出:调用到这里来了
  }

}

那就是说,MapperProxy 拦截了我们所有接口的调用,然后,从这里读取了 mapper.xml 中配置的 SQL 语句,然后调用 jdbc 做相对应的查询。

获取MapperMethod

OK,弄清楚了动态代理,现在回来看看怎么被调用的:

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (method.isDefault()) {
        if (privateLookupInMethod == null) {
          return invokeDefaultMethodJava8(proxy, method, args);
        } else {
          return invokeDefaultMethodJava9(proxy, method, args);
        }
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    // 上面是j8后新增的默认方法的读取,以及判断代理的方法是否是对象
    // 如果哪个为是,直接调用实现的方法

    // 这里开始就是MyBatis自己的实现
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
}


private MapperMethod cachedMapperMethod(Method method) {
    // 从缓存命中,如果没有就创建
    return methodCache.computeIfAbsent(method,
    k -> new MapperMethod(mapperInterface, method,  sqlSession.getConfiguration()));
}

MapperMethod调用

先来看看 MapperMethod 有什么属性,方法肯定就是上面的 execute 是重中之重:

public class MapperMethod {

  // 这是两个内部类对象,一个封装SQL一个封装方法的签名
  private final SqlCommand command;
  private final MethodSignature method;

  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, mapperInterface, method);
  }

  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    // 根据不同的SQL类型去调用不同的方法
    switch (command.getType()) {
      case INSERT: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        // 查询进入这里
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          // 由于主程序只是查询一个,所以跑这里来了

          // 如果带有@Param则要根据名字匹配,如果没有,则根据args的顺序来查找
          Object param = method.convertArgsToSqlCommandParam(args);
          // sqlSession=SqlSessionTemplate,一个对接Spring事务管理器的对象
          result = sqlSession.selectOne(command.getName(), param);
          // 如果是j8的Optional返回的话,需要使用Optional封装结果
          if (method.returnsOptional()
              && (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = Optional.ofNullable(result);
          }
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName()
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }
  // ......
}

SqlSession拦截器

上面的代码我们来到了 sqlSession.selectOne 这句话,所以进入代码看看是发生什么事情:

public class SqlSessionTemplate implements SqlSession, DisposableBean {

  //...
  @Override
  public <T> T selectOne(String statement, Object parameter) {
    return this.sqlSessionProxy.selectOne(statement, parameter);
  }
  //...

  /**
   * Proxy needed to route MyBatis method calls to the proper SqlSession got from Spring's Transaction Manager It also
   * unwraps exceptions thrown by {@code Method#invoke(Object, Object...)} to pass a {@code PersistenceException} to the
   * {@code PersistenceExceptionTranslator}.
   * 翻译:代理需要路由MyBatis去调用SpringTX产生出来的SqlSession
   */
  private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      // 调用下面的方法去获取兼容SpringTX的SqlSession
      SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
      try {
        // 执行方法内容 真正的SqlSession的SelectOne函数
        Object result = method.invoke(sqlSession, args);
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          // force commit even on non-dirty sessions because some databases require
          // a commit/rollback before calling close()
          // 提交事务
          sqlSession.commit(true);
        }
        return result;
      } catch (Throwable t) {
        Throwable unwrapped = unwrapThrowable(t);
        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
          // 也是关闭回滚
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
          sqlSession = null;
          Throwable translated = SqlSessionTemplate.this.exceptionTranslator
              .translateExceptionIfPossible((PersistenceException) unwrapped);
          if (translated != null) {
            unwrapped = translated;
          }
        }
        throw unwrapped;
      } finally {
        // 关闭SqlSession并且将连接放回连接池
        if (sqlSession != null) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
  }

  public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
    notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);

    // 获取SpringTX事务的适配器
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
      return session;
    }

    LOGGER.debug(() -> "Creating a new SqlSession");
    session = sessionFactory.openSession(executorType);

    // 如果有Spring的事务注解的话,则会将事务注册到SpringTX模块中
    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

    return session;
  }

}

查询方法

好,总体的开启 SqlSession 啊,对接事务的,有个了解,现在具体到怎么查询的问题,那就是 SqlSession 了。

算了,下集一起说…….

To Be Continue...