开篇

         说一下写这篇文章的目的,是为了帮助大家梳理一下在spring环境下我们需要做哪些工作去集成mybatis,以及为什么这么去做。好了下面进入我们的本次议题。

mybatis的几个关键类

       首先说一下mybatis的几个关键核心类,及他们的之间的关系。第一个SqlSessionFactory,用来创建SqlSession接口的实例的,默认是DefaultSqlSessionFactory类型的对象。第二个是SqlSession,通过他我们可以管理Connection和获取mapper 映射器实例的动态代理对象,从而进行dao 查询.到此基础的核心类就介绍到这。

与spring集成时需要的操作

      接着说一下和spring集成时的一些操作

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
</bean>
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
  <property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" />
  <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

或者开启包扫描

@Configuration
@MapperScan("org.mybatis.spring.sample.mapper")
public class AppConfig {
  // ...
}

 

好,最后我们总结一下spring集成mybatis所做的两个操作:1 创建sqlSessionFactoryBean   2 通过MapperBeanFactory或自动扫描创建mapper.那么@MapperScan 这个注解为我们做了什么操作,就能够将mapper代理对象放到ioc容器中去呢?

整合实现原理

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({MapperScannerRegistrar.class})//引入这个类
public @interface MapperScan {
    String[] value() default {};

    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};

    Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

    Class<? extends Annotation> annotationClass() default Annotation.class;

    Class<?> markerInterface() default Class.class;

    String sqlSessionTemplateRef() default "";

    String sqlSessionFactoryRef() default "";

    Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
}

 

//mybatis version : 1.3.1.jar
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
    private ResourceLoader resourceLoader;

    public MapperScannerRegistrar() {
    }

    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
        ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
       //此处进行包的扫描
        scanner.doScan(StringUtils.toStringArray(basePackages));
    }

    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }
}
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
        if (beanDefinitions.isEmpty()) {
            this.logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
        } else {
//这个这个函数的关键位置
            this.processBeanDefinitions(beanDefinitions);
        }

        return beanDefinitions;
    }
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
        Iterator var3 = beanDefinitions.iterator();

        while(var3.hasNext()) {
            BeanDefinitionHolder holder = (BeanDefinitionHolder)var3.next();
            GenericBeanDefinition definition = (GenericBeanDefinition)holder.getBeanDefinition();
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + definition.getBeanClassName() + "' mapperInterface");
            }

//将我们原有的bean信息中class 作为构造函数参数传入构造函数            definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
            //将我们扫描到的mapper的bean的信息中class 的信息替换为 mapperFactoryBean.class
            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) {
                    this.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) {
                    this.logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
                }

                definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
                explicitFactoryUsed = true;
            }

            if (!explicitFactoryUsed) {
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
                }
                //并将这个bean设置为属性自动注入,这摸做的目的是将sessionfactory 注入进去
                definition.setAutowireMode(2);
            }
        }

 现在我们容器中的mapper接口现在已经替换为了MapperFactoryBean了。既然他是一个FactoryBean,我们必然会调用.getObject()方法来获取mapper,那么我们在来看看MapperFactoryBean中做了什么?

public T getObject() throws Exception {
//这通过 sqlSession 进行mapper 动态代理的创建,(1)但是sqlSession 是谁创建的呢?(2)默认的sqlSession并不是线程安全的,每个mapper一个mapperFactorybean会不会造成线程不安全问题呢?
        return this.getSqlSession().getMapper(this.mapperInterface);
    }

 

此时我们在关联上上面的设置自动注入属性就会清晰很多了。

//sqlSessionFactory属性注入时 
 public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
        if (!this.externalSqlSession) {
//创建一个SqlSessionTemplate ,我这边需要说明一下,他实现了sqlSession接口,并且它是线程安全的
            this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
        }

    }

到此为止,spring集成mybatis的所有代码流程梳理就介绍完了。

总结

我们来在总结一下:

1) 添加注解@MapperScan时引入MapperScannerRegistrar 

2)在MapperScannerRegistrar 中调用doScan获取扫描包的信息

3) doScan调用processBeanDefinitions()方法

4)在这里面将扫描的Mapper接口的BeanDefinition的信息进行篡改 ,class设置为mapperFactoryBean ,构造函数参数设置为mapper接口名称,设置自动属性注入

5) 自动属性注入时,会注入sqlSessionFctory时创建一个线程安全的sqlSession

6)当我们getBean获取mapper时,会通过getObject方法通过sqlSession.getMapper()方法获取我们所需要的动态代理对象

在spring-boot中的表现

ok,这个MyBatis-spring集成的东西就这样吧。下面我们在来看看springBoot中是如何做的。

//mybatis-spring-boot-autoconfigure version 1.3.0.jar
@Configuration
//mybatis的属性对象封装类,并放入我们bean中
@EnableConfigurationProperties({MybatisProperties.class})
public class MybatisAutoConfiguration {
    ...
    ...
    ...
//创建SqlSessionFactory 
    @Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
     ...
        return factory.getObject();
    }
//创建SqlSessionTemplate
    @Bean
    @ConditionalOnMissingBean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        ExecutorType executorType = this.properties.getExecutorType();
        return executorType != null ? new SqlSessionTemplate(sqlSessionFactory, executorType) : new SqlSessionTemplate(sqlSessionFactory);
    }

    @Configuration
//引入AutoConfiguredMapperScannerRegistrar
    @Import({MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar.class})
    @ConditionalOnMissingBean({MapperFactoryBean.class})
    public static class MapperScannerRegistrarNotFoundConfiguration {
      ...这里面没有什么卵用
    }

    public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar, ResourceLoaderAware {
        private BeanFactory beanFactory;
        private ResourceLoader resourceLoader;

        public AutoConfiguredMapperScannerRegistrar() {
        }

        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            MybatisAutoConfiguration.logger.debug("Searching for mappers annotated with @Mapper");
            ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
                //这又是之前我们的逻辑了
                scanner.doScan(StringUtils.toStringArray(packages));
          }
    }
}