Springboot集成Mybatis源码解析

前言

Mybatis现在作为我们项目中几乎必备的框架,让我们日常开发中操作数据库变得十分简单

那么Mybatis是如何集成到项目中的呢?本文就从源码层面解析Mybatis是如何工作的

源码

我们在项目中使用Mybatis都是优先在pom.xml引入一下jar包

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.0.1</version>
</dependency>

mybatis-spring-boot-starter是Mybatis为了集成到springboot编写的一个starter组件,starter组件基于springboot的自动装配机制简化了配置,自动装配的原理此处不做介绍,可以看这篇

我们直接看jar包下的spring.factories
在这里插入图片描述
也就是springboot在项目启动时会加载MybatisAutoConfiguration

@org.springframework.context.annotation.Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisAutoConfiguration implements InitializingBean {

  private static final Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class);

  private final MybatisProperties properties;

  private final Interceptor[] interceptors;

  private final ResourceLoader resourceLoader;

  private final DatabaseIdProvider databaseIdProvider;

  private final List<ConfigurationCustomizer> configurationCustomizers;

  public MybatisAutoConfiguration(MybatisProperties properties,
      ObjectProvider<Interceptor[]> interceptorsProvider,
      ResourceLoader resourceLoader,
      ObjectProvider<DatabaseIdProvider> databaseIdProvider,
      ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider) {
    this.properties = properties;
    this.interceptors = interceptorsProvider.getIfAvailable();
    this.resourceLoader = resourceLoader;
    this.databaseIdProvider = databaseIdProvider.getIfAvailable();
    this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();
  }

......

  @Bean
  @ConditionalOnMissingBean
  public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
    factory.setDataSource(dataSource);
    factory.setVfs(SpringBootVFS.class);
    ......

    return factory.getObject();
  }

  private void applyConfiguration(SqlSessionFactoryBean factory) {
    Configuration configuration = this.properties.getConfiguration();
    if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
      configuration = new Configuration();
    }
    if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
      for (ConfigurationCustomizer customizer : this.configurationCustomizers) {
        customizer.customize(configuration);
      }
    }
    factory.setConfiguration(configuration);
  }

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

   ......
  }

  /**
   * {@link org.mybatis.spring.annotation.MapperScan} ultimately ends up
   * creating instances of {@link MapperFactoryBean}. If
   * {@link org.mybatis.spring.annotation.MapperScan} is used then this
   * auto-configuration is not needed. If it is _not_ used, however, then this
   * will bring in a bean registrar and automatically register components based
   * on the same component-scanning path as Spring Boot itself.
   */
  @org.springframework.context.annotation.Configuration
  @Import({ AutoConfiguredMapperScannerRegistrar.class })
  @ConditionalOnMissingBean(MapperFactoryBean.class)
  public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {

    @Override
    public void afterPropertiesSet() {
      logger.debug("No {} found.", MapperFactoryBean.class.getName());
    }
  }

}

这里代码太长了,省略了一下不重要的代码,分析下里面几个主要的bean

  1. SqlSessionFactory,在Mybatis里面主要用于产生SqlSession,提供操作数据库的增删改查
  2. SqlSessionTemplate是SqlSession接口的一个实现类,以前SqlSession的默认实现类DefaultSqlSession不是线程安全的,所以出现了线程安全的SqlSessionTemplate,并且注册为了bean
  3. applyConfiguration方法主要是解析了Configuration对象,并且放到了SqlSessionFactory里面,Configuration对象在Mybatis也很重要,主要是存储了各种配置
  4. MapperScannerRegistrarNotFoundConfiguration,在这里最主要的一个静态类,接下来会讲

MapperScannerRegistrarNotFoundConfiguration类上有个@Import({ AutoConfiguredMapperScannerRegistrar.class }),所以会加载AutoConfiguredMapperScannerRegistrar类,AutoConfiguredMapperScannerRegistrar是MybatisAutoConfiguration的一个静态内部类,实现了spring的ImportBeanDefinitionRegistrar,所以会执行registerBeanDefinitions方法

public static class AutoConfiguredMapperScannerRegistrar
      implements BeanFactoryAware, ImportBeanDefinitionRegistrar, ResourceLoaderAware {

    private BeanFactory beanFactory;

    private ResourceLoader resourceLoader;

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

      ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
      if (this.resourceLoader != null) {
        scanner.setResourceLoader(this.resourceLoader);
      }
      scanner.setAnnotationClass(Mapper.class);
      scanner.registerFilters();
      // 核心代码
      scanner.doScan(StringUtils.toStringArray(packages));

    }
......
  }

接下来我们主要看核心代码,看scanner.doScan,传入了我们packages路径,packages路径就是我们配置的注解@MapperScan的value或者basePackages,也就是Mybatis要扫描的包路径,并且调用了scanner.setAnnotationClass(Mapper.class);也就是扫描的注解

看doScan

public Set<BeanDefinitionHolder> doScan(String... basePackages) {
	// 核心代码1
    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 {
      // 核心代码2
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
  }

核心代码1处,调用的是spring提供的doScan,也就是扫描该路径下配置有@Mapper注解的bean。

这里先找到了BeanDefinitionHolder的集合,BeanDefinitionHolder可以认为是spring对bean的封装

接下来调用了processBeanDefinitions

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

      // the mapper interface is the original class of the bean
      // but, the actual class of the bean is MapperFactoryBean
      definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
      // 核心代码
      definition.setBeanClass(this.mapperFactoryBeanClass);

    ......
    }
  }

这里最重要的就是设置了beanClass为mapperFactoryBeanClass,mapperFactoryBeanClass的定义

private Class<? extends MapperFactoryBean> mapperFactoryBeanClass = MapperFactoryBean.class;

到这里为止已经成功的把我们Mybatis的Mapper接口注册到spring的beanDefinitionMap容器中了,beanDefinitionMap 的定义

private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(64);

最终spring会根据beanDefinitionMap 注册ioc容器,注册后我们的Mybatis Mapper的bean的存储是一个Map,key就是我们的Mapper接口首字母小写的名称,value会根据我们上面设置的beanClass创建出一个MapperFactoryBean,这个MapperFactoryBean很关键,我们看下他的定义

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

  private Class<T> mapperInterface;

  private boolean addToConfig = true;

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

  /**
   * {@inheritDoc}
   */
  @Override
  protected void checkDaoConfig() {
    super.checkDaoConfig();

    notNull(this.mapperInterface, "Property 'mapperInterface' is required");

    Configuration configuration = getSqlSession().getConfiguration();
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
      try {
        configuration.addMapper(this.mapperInterface);
      } catch (Exception e) {
        logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
        throw new IllegalArgumentException(e);
      } finally {
        ErrorContext.instance().reset();
      }
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Class<T> getObjectType() {
    return this.mapperInterface;
  }

 ......

  /**
   * Return the mapper interface of the MyBatis mapper
   *
   * @return class of the interface
   */
  public Class<T> getMapperInterface() {
    return mapperInterface;
  }
}

这个MapperFactoryBean继承了spring提供的FactoryBean,在spring创建bean时会调用它的getObject方法,这个getObject定义如下

  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }

到了SqlSessionManager的getMapper

public <T> T getMapper(Class<T> type) {
    return getConfiguration().getMapper(type, this);
  }

到了Configuration的getMapper

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

MapperRegistry的getMapper

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 T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

newInstance方法

protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

这里可以看到使用了jdk的动态代理,最终返回了一个MapperProxy,也就是说我们Mybatis最终注入的Mapper类实际上他的实现类是一个MapperProxy对象

MapperProxy是一个实现了InvocationHandler接口的实现类,基于动态代理,调用Mapper的任意方法时都会进入到其invoke方法

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 (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

到这里基本上就进入了Mybatis的核心代码了,基于调用Mapper接口的方法,找到对应的xml文件里面的sql,然后动态执行sql,最终返回

总结

到这里Mybatis在springboot中是如何加载的已经分析完毕了,其中核心的就是基于springboot自动装配机制来注册自己的bean,然后使用AutoConfiguredMapperScannerRegistrar来扫描Mapper接口,到ioc容器注册对应的bean,然后bean的真实实现类是MapperProxy代理类,基于动态代理来实现sql语句的执行

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值