Mybatis Spring 整合原理

4 篇文章 0 订阅
1 篇文章 0 订阅

本文属于个人在学习过程中的笔记,本文不是讲解如何整合,只涉及相关原理。

总的来说涉及Spring bean生命周期、jdk动态代理技术,最终达到mybatis与spring的整合。

两者整合后的主要特点:只需定义接口无需编写实现即可调用对应实现方法,完成数据获取。

整合实际是mapper的注册、注入,注册mapper 不需要我们一个一个注册,而是使用了扫描机制

在Mybatis-Spring 1.2.0及以上版本,有三种方式:

  1. <mybatis:scan/>
  2. 注解@MapperScan
  3. 配置MapperScannerConfigurer 

(具体如何使用 请查看官方文档 如:http://www.mybatis.org/spring/mappers.html)

目前大家最常用的是第三种方式,现在新开发的项目基本上都是基于spring-boot,也就是使用mybatis-spring-boot-starter,这只不过是自动配置了,它实际还是依赖的MapperScannerConfiger,具体代码见 MybatisAutoConfiguration

因此我们就看它的源码:MapperScannerConfigurer 

搜索base 包中所有 interface,并将其注册到 Spring Bean容器中,其注册的class bean是MapperFactoryBean

关键方法

@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);
  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.registerFilters();
  scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}

但是实际的扫描和注册bean在 ClassPathMapperScanner类中

关键方法

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

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
  GenericBeanDefinition definition;
  for (BeanDefinitionHolder holder : beanDefinitions) {
    definition = (GenericBeanDefinition) holder.getBeanDefinition();

    if (logger.isDebugEnabled()) {
      logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() 
        + "' and '" + definition.getBeanClassName() + "' mapperInterface");
    }

    // the mapper interface is the original class of the bean
    // but, the actual class of the bean is MapperFactoryBean
    definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
    definition.setBeanClass(this.mapperFactoryBean.getClass());

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

    boolean explicitFactoryUsed = false;
    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) {
      if (logger.isDebugEnabled()) {
        logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
      }
      definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
    }
  }
}

从上面的关键代码看出,最终的这些bean的实现类是MapperFactoryBean,从名称可以看出这是一个生成Mapper的工厂。

definition.setBeanClass(this.mapperFactoryBean.getClass());

另外ClassPathMapperScanner默认情况扫描的是接口类,具体代码见其中方法:

  @Override
  protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
    return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
  }

MapperFactoryBean实现了接口 FactoryBean,因此它里面的关键方法

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

getSqlSession()方法返回的是一个接口 SqlSession ,此接口有三个实现,通过debug (这里还需要研究为什么是)我们可以看到使用的是 SqlSessionTemplate

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

然后顺着向下,直到类 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 (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与spring的整合原理的关键点:

1、有一个实现了BeanDefinitionRegistryPostProcessor接口的MapperScannerConfigurer,它负责在指定包下扫描对应的接口为beanDefinitions且它对应的BeanClass为MapperFactoryBean

2、MapperFactoryBean实现了FactoryBean接口,spring在注入mapper时,就实际为getObject()的返回对象,返回的是MapperProxy的实例

3、MapperProxy 实际是对mapper接口的一个动态代理(Java基于接口的动态代理)

4、后面的是Mybatis框架的最为核心的功能,简单理解就是mybatis把对应sql都解析好了并缓存了,然后执行sql,解析结果和返回结果,具体详情可以看类 MapperProxy

另:Spring Boot情况下,实际就是在步骤1前面有一个自动装配的实现,具体详情,查看mybatis-spring-boot-autoconfigure.jar下的类MybatisAutoConfiguration的代码实现

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值