Mybatis源码分析(五)mybatis和spring整合

之前单独使用Mybatis的时候,是用SqlSession得到一个mapper然后调用mapper里面对应的方法就可以得到数据库中的数据。

代码如下:

public static void main(String[] args) throws IOException {
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    try (SqlSession session = sqlSessionFactory.openSession()) {
//    Configuration中维护了一个MapperRegistry 通过它返回 HRMapper的一个动态代理
      HRMapper mapper = session.getMapper(HRMapper.class);
      List<HR> hrs = mapper.selectBlog(3L);
      System.out.println(hrs.get(0).getName());
    }
  }

 现在知道, session.getMapper(HRMapper.class);  返回的只是HRMapper的代理对象,代理逻辑,解析sql,操作数据库等操作Mybatis都封装好了。

 在spring中使用Mybatis ,一般都是直接:通过注解得到的,顺着这个思路,只要把上面mybatis中得到的代理对象交给spring管理,就可以在spring中使用代理对象来操作数据库了。

@Autowired
UserMapper userMapper;

 


 

1:dataSource 数据源肯定要配置  2:SqlSession肯定要配置,mybatis就是靠它获取到mapper的代理对象的  3:生成的mapper代理对象要动态的注册到spring容器中。

前两个直接配置@Bean就可以了,需要用的时候配置。第三个问题,需要中间件mybatis-spring来解决了。

一般我们会用到@MapperScan注解,它可以把某个包下面的接口,变成代理对象注册到容器中。看下它是怎么做到的。

@MapperScan 源码解析

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {
sqlSessionFactoryRef :  注解属性,在配置中可以定义多个SqlSessionFactory,可以使用这个属性指定使用那个。
sqlSessionTemplateRef:  注解属性,在配置中可以定义多个SqlSessionTemplate,可以使用这个属性指定使用那个。
annotationClass  :       注解属性,加上某个注解之后(在SpringBoot中会用到Mapper.class),mapper的扫描器,会只把有这个注解的mapper扫描出来。默认就没什么东西,只要是注解就可以

}

 

 

引入了一个 MapperScannerRegistrar 类。

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

  private ResourceLoader resourceLoader;

  /**
   * {@inheritDoc}
   */
  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

    AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    //  定义了一个Mapper的扫描器
ClassPathMapperScanner scanner
= new ClassPathMapperScanner(registry); // this check is needed in Spring 3.1 if (resourceLoader != null) { scanner.setResourceLoader(resourceLoader); } Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass"); if (!Annotation.class.equals(annotationClass)) { scanner.setAnnotationClass(annotationClass); } Class<?> markerInterface = annoAttrs.getClass("markerInterface"); if (!Class.class.equals(markerInterface)) { scanner.setMarkerInterface(markerInterface); } Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator"); if (!BeanNameGenerator.class.equals(generatorClass)) { scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass)); } Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean"); if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) { scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass)); } scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef")); scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef")); List<String> basePackages = new ArrayList<String>(); for (String pkg : annoAttrs.getStringArray("value")) { if (StringUtils.hasText(pkg)) { basePackages.add(pkg); } } for (String pkg : annoAttrs.getStringArray("basePackages")) { if (StringUtils.hasText(pkg)) { basePackages.add(pkg); } } for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) { basePackages.add(ClassUtils.getPackageName(clazz)); } scanner.registerFilters(); scanner.doScan(StringUtils.toStringArray(basePackages)); } /** * {@inheritDoc} */ @Override public void setResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; } }
ClassPathMapperScanner 扫描器它继承了Spring中的ClassPathBeanDefinitionScanner扫描器

 

 

 

 而且在构造函数中,把是否使用默认的过滤器设置为了false,因为在Spring中 ClassPathBeanDefinitionScanner是使用默认的过滤器的,而且默认是过滤@Component注解。

 

 

 还有一点,在Spring中的ClassPathBeanDefinitionScanner扫描器的父类ClassPathScanningCandidateComponentProvider 中有一个方法 isCandidateComponent,它是判断

扫描出来的类是不是接口,如果是接口,则认为不是候选者bean.

    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        AnnotationMetadata metadata = beanDefinition.getMetadata();
        // 不是接口或抽象类,如果是抽象类那么抽象类上得是Lookup注解
        return (metadata.isIndependent() && (metadata.isConcrete() ||
                (metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
    }

 

但是在mybatis中这种逻辑肯定不对的,所以在ClassPathMapperScanner中就重写了这个方法,

 protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
// 只有接口才认为是候选者bean
return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent(); }

 

doScan扫描逻辑:

  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
// 调用父类的扫描逻辑,这时候就会把那些mapper接口下面的接口都扫描出来了
// 这个时候BeanDefinition中的class还是接口的全限定类名
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;
// 遍历扫描出来的接口的BeanDefinition
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
// 在和Spring整合之后,mapper动态代理的生成是靠一个FactoryBean生成的,就是MapperFactoryBean
// 这个MapperFactoryBean是要根据不同的mapper来生成不同的代理对象,所以这个FactoryBean的构造函数中传入的Mapper的全限定类名 就是BD存储的beanClassName
// 然后修改BD中的beanClass为FactoryBean,这样在Srping中注入使用mapper的时候,就会使用FactoryBean中的getObject方法返回一个代理对象了 definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59 definition.setBeanClass(this.mapperFactoryBean.getClass()); definition.getPropertyValues().add("addToConfig", this.addToConfig); boolean explicitFactoryUsed = false;
// 如果在MapperScan注解中指定了sqlSessionFactoryRef,这里添加进去
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; } // 这里也是指定的 如果指定了这两个参数 explicitFactoryUsed设置为false 不再用默认的 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; }
// 如果上面没有指定则使用By_TYPE的注入类型 这种注入方式会根据set方法注入
if (!explicitFactoryUsed) { if (logger.isDebugEnabled()) { logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'."); } definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); } } }

 

我们看下 MapperFactoryBean的逻辑

// 继承了 SqlSessionDaoSupport
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}
getSqlSession().getMapper(mapperInterface) 可以看出这一步就是和单独使用mybatis是一样的,都是通过sqlSession得到mapper的代理对象
但是这里的getSqlSession()方法实在父类中的
*/ @Override public T getObject() throws Exception { return getSqlSession().getMapper(this.mapperInterface); } /** * {@inheritDoc} */ @Override public Class<T> getObjectType() { return this.mapperInterface; } /** * {@inheritDoc} */ @Override public boolean isSingleton() { return true; } //------------- mutators -------------- /** * Sets the mapper interface of the MyBatis mapper * * @param mapperInterface class of the interface */ public void setMapperInterface(Class<T> mapperInterface) { this.mapperInterface = mapperInterface; } /** * Return the mapper interface of the MyBatis mapper * * @return class of the interface */ public Class<T> getMapperInterface() { return mapperInterface; } /** * If addToConfig is false the mapper will not be added to MyBatis. This means * it must have been included in mybatis-config.xml. * <p/> * If it is true, the mapper will be added to MyBatis in the case it is not already * registered. * <p/> * By default addToCofig is true. * * @param addToConfig */ public void setAddToConfig(boolean addToConfig) { this.addToConfig = addToConfig; } /** * Return the flag for addition into MyBatis config. * * @return true if the mapper will be added to MyBatis in the case it is not already * registered. */ public boolean isAddToConfig() { return addToConfig; } }

 

父类 SqlSessionDaoSupport

public abstract class SqlSessionDaoSupport extends DaoSupport {

  private SqlSession sqlSession;

  private boolean externalSqlSession;
//  就算传入SqlSessionFactory,也是封装成SqlSessionTemplate
  public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    if (!this.externalSqlSession) {
      this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
    }
  }

  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
this.sqlSession是否上面两个set赋值的,上面两个set方法会在根据BY_TYPE属性注入的时候调用
*/ public SqlSession getSqlSession() { return this.sqlSession; } /** * {@inheritDoc} */ @Override protected void checkDaoConfig() { notNull(this.sqlSession, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required"); } }

 

在整合的时候定义了下面的这个类:

@Bean
    public SqlSessionFactory sqlSessionFactory() {
        SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
        sessionFactoryBean.setDataSource(dataSource());
        try {
            return sessionFactoryBean.getObject();
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

 

如果不在MapperScan中指定,默认就会根据BY_TYPTE生成MapperFactoryBean的实例,根据其父类的 setSqlSessionFactory方法,赋值给SqlSession。但是这时候的SqlSession不是在Mybatis中用到的DefaultSqlSession了,而是 SqlSessionTemplate。

new SqlSessionTemplate(sqlSessionFactory); 继续往下走就会走到下面的构造方法:

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
    notNull(executorType, "Property 'executorType' is required");

    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
    this.sqlSessionProxy = (SqlSession) newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class },
        new SqlSessionInterceptor());
  }

 

这里又生成了一个动态代理,代理的目标对象就是SqlSession,代理逻辑就是SqlSessionInterceptor,在SqlSessionTemplate中调用的执行方法,selectOne,selectList,selectMap等都是调用的sqlSessionProxy代理逻辑。

 

 

 看下SqlSessionInterceptor的代理逻辑,它是SqlSessionTemplate的一个内部类:

 

 

 看到invoke方法中,又重新得到的了一个SqlSession

SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);

 

这个getSqlSession是从SqlSessionUtils静态导入的

 

 

 具体的逻辑:

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory) {
// 得到默认的执行器类型 ExecutorType executorType
= sessionFactory.getConfiguration().getDefaultExecutorType(); return getSqlSession(sessionFactory, executorType, null); }

 

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

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

    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

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

    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Creating a new SqlSession");
    }
// 回到mybatis中的获取sqlSession的逻辑
    session = sessionFactory.openSession(executorType);

    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

    return session;
  }

 

上面mybatis-spring整合是老版本的 1.3.2的。

在新版本2.0.5。中MapperScan注解导入的bean是下面这样的:

 

 

 注入了一个MapperScannerRegistrar,

 

 

 它实现了ImportBeanDefinitionRegistrar接口说明有注册bean的功能。看注册逻辑:

 

 

注入了一个 MapperScannerConfigurer类,这个类又实现了 BeanDefinitionRegistryPostProcessor接口,所以会重写 postProcessBeanDefinitionRegistry方法,

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.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
    if (StringUtils.hasText(lazyInitialization)) {
      scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
    }
    scanner.registerFilters();
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

 

这里的有扫描的逻辑了,和之前的那个是一样的了。

其实新版本就是做了一层MapperScannerConfigurer的封装,这样就可以不用MapperScan注解,可以直接注入MapperScannerConfigurer,可以自定义写相关的属性控制。

 

posted @ 2021-07-25 15:10  蒙恬括  阅读(235)  评论(0编辑  收藏  举报