侧边栏壁纸
博主头像
再见理想博主等级

只争朝夕,不负韶华

  • 累计撰写 112 篇文章
  • 累计创建 64 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录

spring整合mybatis源码分析

再见理想
2022-11-30 / 0 评论 / 0 点赞 / 542 阅读 / 1,243 字

一,前言

MyBatis可以独立于Spring使用,这里我们关注一下Mybatis和Spring是怎么整合在一起的。在Spring中使用MyBatis时我们只需要定义一个mapper接口,并配置好对应的mapper.xml,这样就可以直接通过mapper接口直接执行数据库操作。但是我们并没有手动的实例化该接口,那它是如何进行实例化并加入spring容器的呢?

二,Mybatis-Spring 1.3.2 版本源码执行流程

1,通过入口 @MapperScan 导入了 MapperScannerRegistrar 类,用于扫描 Mapper 接口并生成对应代理对象,注入到 IOC 容器;

@Configuration
@MapperScan({"com.base.core.mapper"})
public class MybatisPlusConfig {
}
// org.mybatis.spring.annotation.MapperScan
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {
}

2,MapperScannerRegistrar 类实现了 ImportBeanDefinitionRegistrar 接口,所以 Spring 在启动时会调用MapperScannerRegistrar 类中的 registerBeanDefinitions() 方法,扫描接口并生成对应 BeanDefinition;

3,在 registerBeanDefinitions 方法中定义了一个 ClassPathMapperScanner 对象,用来扫描 mapper 接口,因为在 Spring 中是不会扫描接口的,同时因为 ClassPathMapperScanner 中重写了 isCandidateComponent 方法,默认只扫描接口;

// org.mybatis.spring.annotation.MapperScannerRegistrar#registerBeanDefinitions

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
    
    private ResourceLoader resourceLoader;
    
	@Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

    AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    //定义ClassPathMapperScanner对象,用来扫描mapper接口
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    // 省略 ...
    scanner.registerFilters();
    // 扫描mapper接口,并生成对应 BeanDefinition 
    scanner.doScan(StringUtils.toStringArray(basePackages));
  }
}
// org.mybatis.spring.mapper.ClassPathMapperScanner#isCandidateComponent
  protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
    return beanDefinition.getMetadata().isInterface() && 	     beanDefinition.getMetadata().isIndependent();
  }

4,通过利用 Spring 的扫描后,接下来把扫描得到的 BeanDefinition 进行修改,把 BeanClass 修改为 MapperFactoryBean,把 AutowireMode 修改为 byType ;扫描完成后,Spring 就会基于 BeanDefinition 去创建 Bean 了,相当于每个 Mapper 对应一个FactoryBean(单例);

// org.mybatis.spring.mapper.ClassPathMapperScanner#doScan
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 {
      // BeanDefinition 加工
      processBeanDefinitions(beanDefinitions);
    }
    return beanDefinitions;
  }

接下来看看 processBeanDefinitions(beanDefinitions) 方法

// org.mybatis.spring.mapper.ClassPathMapperScanner#processBeanDefinitions
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
      // 把 BeanClass 修改为 MapperFactoryBean
      definition.setBeanClass(this.mapperFactoryBean.getClass());
      // 省略 ...
      // 把 AutowireMode 修改为 byType
      definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
    }
  }

5,在 MapperFactoryBean 中 的 getObject() 方法中,调用了 getSqlSession() 去得到一个 sqlSession 对象,然后根据对应的 Mapper 接口生成一个代理对象。

前面扫描步骤 doScan 已经设置 MapperFactoryBean 的 AutowireMode 为 byType,所以Spring会自动调用set方法。SqlSessionDaoSupport 有两个set方法,一个 setSqlSessionFactory,一个setSqlSessionTemplate,而这两个方法执行的前提是根据方法参数类型能找到对应的 bean,所以 Spring 容器中要存在 SqlSessionFactory 类型的 bean 或者 SqlSessionTemplate 类型的 bean;

如果你定义的是一个 SqlSessionFactory 类型的 bean,那么最终也会被包装为一个 SqlSessionTemplate 对象,并且赋值给 sqlSession 属性;

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

下面看看 SqlSessionDaoSupport 的 getSqlSession() 方法:

// org.mybatis.spring.support.SqlSessionDaoSupport#getSqlSession
public abstract class SqlSessionDaoSupport extends DaoSupport {

  private SqlSession sqlSession;

  private boolean externalSqlSession;
    
  // 通过setSqlSessionFactory来设置 SqlSession
  public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    if (!this.externalSqlSession) {
      // 最终也会被包装为一个SqlSessionTemplate对象,并且赋值给sqlSession属性;
      this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
    }
  }
  // 通过 setSqlSessionTemplate 来设置 SqlSession
  public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
    this.sqlSession = sqlSessionTemplate;
    this.externalSqlSession = true;
  }

  /**
   * Users should use this method to get a SqlSession to call its statement methods
   * This is SqlSession is managed by spring. Users should not commit/rollback/close it
   * because it will be automatically done.
   *
   * @return Spring managed thread safe SqlSession
   */
  public SqlSession getSqlSession() {
    return this.sqlSession;
  }
}

而在 SqlSessionTemplate 类中就存在一个 getMapper 方法,这个方法中就会利用 SqlSessionFactory 来生成一个代理对象;到时候,当执行该代理对象的某个方法时,就会进入到Mybatis框架的底层执行流程。

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

流程简图:

三,Mybatis-Spring 2.0.6 版本源码执行流程

  1. 通过@MapperScan导入了MapperScannerRegistrar类
  2. MapperScannerRegistrar类实现了ImportBeanDefinitionRegistrar接口,所以Spring在启动 时会调用MapperScannerRegistrar类中的registerBeanDefinitions方法
  3. 在registerBeanDefinitions方法中注册一个MapperScannerConfigurer类型的 BeanDefinition
  4. 而MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,所以 Spring在启动过程中时会调用它的postProcessBeanDefinitionRegistry()方法
  5. 在postProcessBeanDefinitionRegistry方法中会生成一个ClassPathMapperScanner对象,然 后进行扫描
  6. 后续的逻辑和1.3.2版本一样。 带来的好处是,可以不使用@MapperScan注解,而可以直接定义一个Bean,比如:
@Bean
public MapperScannerConfigurer mapperScannerConfigurer() {
    MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
    mapperScannerConfigurer.setBasePackage("com.base.core.mapper");
    return mapperScannerConfigurer;
}
0

评论区