zoukankan      html  css  js  c++  java
  • 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做了一些自定配置等。

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

  • 相关阅读:
    HTML之元素分类(HTML基础知识)
    GreenPlum 与hadoop什么关系?(转)
    安装配置MySQL
    Linux下安装jdk步骤
    Linux ssh无密码登录
    左右无间切换走马灯angularJS指令
    CSS3制作立方体--有趣的应用
    hello,2017
    渐进增强与优雅降级
    图片上传预览(包含大小压缩)
  • 原文地址:https://www.cnblogs.com/54chensongxia/p/11850709.html
Copyright © 2011-2022 走看看