MyBatis整合Spring原理分析



假如不结合Spring框架,我们使用MyBatis时的一个典型使用方式如下:

public class UserDaoTest {        private SqlSessionFactory sqlSessionFactory;        @Before      public void setUp() throws Exception{          ClassPathResource resource = new ClassPathResource("mybatis-config.xml");          InputStream inputStream = resource.getInputStream();          sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);      }        @Test      public void selectUserTest(){          String id = "{0003CCCA-AEA9-4A1E-A3CC-06D884BA3906}";          SqlSession sqlSession = sqlSessionFactory.openSession();          CbondissuerMapper cbondissuerMapper = sqlSession.getMapper(CbondissuerMapper.class);          Cbondissuer cbondissuer = cbondissuerMapper.selectByPrimaryKey(id);          System.out.println(cbondissuer);          sqlSession.close();      }    }  

我们首先需要SqlSessionFactory,然后通过SqlSessionFactory的openSession方法获得SqlSession。通过SqlSession获得我们定义的接口的动态代理类(MapperProxy)。当我们整合Spring框架时,我们使用MyBatis的方式简单的“难以想象”:

@Autowore  private CbondissuerMapper cbondissuerMapper;  

非常Spring形式的使用方式,直接注入就可以了。那这个是怎么实现的呢?Spring是怎么把上面略显复杂的模板代码省略的呢?我的第一直觉是Spring在启动的时候做了Mybatis的初始化工作,然后一次性获取了所有Mapper接口的动态代理实现类并将其放入Spring容器中进行管理。

下面来验证下自己的猜想对不对。

MyBatis整合Spring原理分析

下面以Spring Boot中的MyBatisAutoConfigration为列子做下简单分析:

  //SqlSessionFactoryBean的最终作用就是解析MyBati配置文件,并最终生成Configration对象,然后生成DefaultSqlSessionFactory,并加入Spring的容器管理。可以看出SqlSessionFactoryBean的作用和SqlSessionFactoryBeanBuilder的作用很像。    @Bean    @ConditionalOnMissingBean    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {      SqlSessionFactoryBean factory = new SqlSessionFactoryBean();      factory.setDataSource(dataSource);      factory.setVfs(SpringBootVFS.class);      if (StringUtils.hasText(this.properties.getConfigLocation())) {        factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));      }      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);      if (this.properties.getConfigurationProperties() != null) {        factory.setConfigurationProperties(this.properties.getConfigurationProperties());      }      if (!ObjectUtils.isEmpty(this.interceptors)) {        factory.setPlugins(this.interceptors);      }      if (this.databaseIdProvider != null) {        factory.setDatabaseIdProvider(this.databaseIdProvider);      }      if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {        factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());      }      if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {        factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());      }      if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {        factory.setMapperLocations(this.properties.resolveMapperLocations());      }        return factory.getObject();    }      //创建SqlSessionTemplate这个Bean,这个类动态代理了DefaultSqlSession,所以最后还是调用了DefaultSqlSession,下面会重点分析下SqlSessionTemplate    @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);      }    }  

以下是SqlSessionTemplate的源代码,由于源码较多,只贴出重点部分:

public class SqlSessionTemplate implements SqlSession, DisposableBean {      private final SqlSessionFactory sqlSessionFactory;      private final ExecutorType executorType;    //SqlSessionTemplate动态代理了DefaultSqlSession,所用对sqlSessionProxy的调用都会经过代理对象    private final SqlSession sqlSessionProxy;      private final PersistenceExceptionTranslator exceptionTranslator;      ....      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());    }      ....    //对sqlSessionProxy的CRUD调用都会先调用到这里    private class SqlSessionInterceptor implements InvocationHandler {      @Override      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        //这步相当于调用sqlSessionFactory的openSesion方法获得SqlSession        SqlSession sqlSession = getSqlSession(            SqlSessionTemplate.this.sqlSessionFactory,            SqlSessionTemplate.this.executorType,            SqlSessionTemplate.this.exceptionTranslator);        try {          //调用DefaultSqlSession的CRUD方法          Object result = method.invoke(sqlSession, args);          if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {            sqlSession.commit(true);          }          return result;        } catch (Throwable t) {          Throwable unwrapped = unwrapThrowable(t);          if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {            // release the connection to avoid a deadlock if the translator is no loaded. See issue #22            closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);            sqlSession = null;            Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);            if (translated != null) {              unwrapped = translated;            }          }          throw unwrapped;        } finally {          if (sqlSession != null) {            //最后强制关闭SqlSession            closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);          }        }      }    }  }  

到此为止,整个Spring中获取SqlSessionFactory、获得SqlSession、执行CRUD、以及关闭SqlSession的流程都进行分析了。还有一个有疑问的地方就是Mapper接口的实现类是在什么时候动态生成的。

MapperScan的秘密

我们知道MapperScan是用来扫描Mapper接口的,所以很自然的想到去MapperScan这个类里面一探究竟。

@Retention(RetentionPolicy.RUNTIME)  @Target(ElementType.TYPE)  @Documented  //秘密在MapperScannerRegistrar里面  @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 "";    //指定自定义的MapperFactoryBean    Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;    }  

下面就看下MapperScannerRegistrar里面做了什么:

public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {        AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));      //创建了一个ClassPathMapperScanner,并根据@MapperScan里面的配置设置属性      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));    }  

以上创建了一个ClassPathMapperScanner,并根据@MapperScan里面的配置设置属性,开始扫描。下面再看ClassPathMapperScanner。

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");        }   definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59        //这边是重点,将扫描到的Mapper接口的BeanClass设置为MapperFactoryBrean        //MapperFactoryBrean是工厂Bean,用于生成MapperProxy;        //Spring在自动注入Mapper的时候会自动调用这个工厂Bean的getObject方法,生成MapperProxy并放入Spring容器。        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);        }      }    }  

到此Spring自动生成Mapper接口实现类的过程也分析完了。

简单总结

通过以上分析,印证了一开始的猜想:Spring在启动的时候做了Mybatis的初始化工作,然后一次性获取了所有Mapper接口的动态代理实现类并将其放入Spring容器中进行管理。大致流程如下:

  • 配置SqlSessionFactoryBean,这个Bean的最终作用是创建DefaultSqlSessionFactory,并将DefaultSqlSessionFactory加入Spring容器管理;
  • 创建SqlSessionTemplate,这个对象代理了MyBatis中的DefaultSqlSession,通过SqlSessionTemplate调用任何CRUD方法都会经历openSession、调用DefaultSqlSession的CRUD方法和关闭Session的过程;

Mapper接口生成的流程大致如下:

  • @MapperScan注解扫描到所有的Mapper接口生成相应的BeanDefinition;
  • ClassPathMapperScanner的处理方法对这些BeanDefinition做进一步配置,其中最终要的一步就是将这些BeanDefinition的Beanclass属性设置为MapperFactoryBean(这是一个工厂Bean,专门用于生成Mapper的动态代理实现MapperProxy,一个Dao接口会对应一个MapperFactoryBean);
  • Spring在检测到需要自动注入Mapper时通过层层调用,最终会调用到MapperFactoryBean这个工厂Bean的getObject方法生成对应的MapperProxy,并将这个对象纳入Spring管理。

以上大致就是MyBatis整合进Spring的原理。我们发现其实质和传统的Mybatis使用是一样的,只不过是通过Spring做了一些自定配置等。

分析的有点乱,先这样吧~