美文网首页
mybatis spring介绍、使用、实现原理

mybatis spring介绍、使用、实现原理

作者: 高级Java开发 | 来源:发表于2021-12-18 15:31 被阅读0次

    maven依赖

    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>2.0.6</version>
    </dependency>
    

    使用

    SqlSessionFactoryBean 可以创建sqlSessionFactory,dataSource是自己的数据源的bean @MapperScan注解可以帮助我们把mybatis的Mapper类注册为bean,这样我们就可以在使用的地方通过@Autowired/@Resource引用来使用。

    @MapperScan("com.github.liuzhengyang")
    @Configuration
    public class MyBatisConfig {
        @Autowired
        @Bean
        public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
            SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
            sqlSessionFactoryBean.setDataSource(dataSource);
            return sqlSessionFactoryBean;
        }
    }
    

    实现原理

    mybatis-spring帮助我们简化的工作、带来的价值是

    • SqlSessionFactory创建的工作,使用原生的mybatis需要用配置文件配置mybatis,mybais-spring可以用@Bean代码创建。(虽然mybatis也能用代码构建,不过SqlSessionFactory被spring管理了,在使用的时候只需要@Autowire会更方便)
    • 不再需要每次openSession、close,这些工作在mybatis-spring内部实现了,mybatis-spring帮助我们判断是否要openSession
    • Mapper类变成了bean,需要使用的时候直接@Autowired就可以
    • 提供线程安全的SqlSessionTemplate

    SqlSessionFactory如何创建

    SqlSessionFactory通过SqlSessionFactoryBean#buildSqlSessionFactory构建,调用时机是SqlSessionFactoryBean.afterPropertiesSet。 SqlSessionFactory有大量可配置项,这些配置项最终转变为SqlSessionFactory的构建参数(Configuration)

    @Override
      public void afterPropertiesSet() throws Exception {
        notNull(dataSource, "Property 'dataSource' is required");
        notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
        state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
            "Property 'configuration' and 'configLocation' can not specified with together");
        this.sqlSessionFactory = buildSqlSessionFactory();
    }
    protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
        final Configuration targetConfiguration;
        ...
        XMLConfigBuilder xmlConfigBuilder = null;
        if (this.configuration != null) {
          targetConfiguration = this.configuration;
          if (targetConfiguration.getVariables() == null) {
            targetConfiguration.setVariables(this.configurationProperties);
          } else if (this.configurationProperties != null) {
            targetConfiguration.getVariables().putAll(this.configurationProperties);
          }
        } else if (this.configLocation != null) {
          xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
          targetConfiguration = xmlConfigBuilder.getConfiguration();
        } else {
          LOGGER.debug(
              () -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
          targetConfiguration = new Configuration();
          Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
        }
        ...
    
        return this.sqlSessionFactoryBuilder.build(targetConfiguration);
    }
    

    Mapper类如何注册成bean到BeanFactory中的

    使用了mybatis-spring,会扫描特定的Mapper类(@MapperScan注解控制,控制标注了任意注解的接口可以被注册上,还可以配置接口的parent判断),然后作为Bean注册到beanFactory中,从而能被其他的bean依赖使用。

    @Mapper
    public interface UserMapper {
        User getUserById(long id);
    }
    

    要实现这样的scan机制,就需要一个scan mapper的BeanPostProcessor,这个processor中,scan当前classpath下满足MapperScan配置的package要求的类(接口),并且判断是否有@Mapper注解,如果符合,创建BeanDefinition注册到BeanFactory中。在getBean的时候,调用MapperFactoryBean.getObject拿到的Mapper代理,实现是Configuration.getMapper(Class type, SqlSession sqlSession), SqlSession是SqlSessionTemplate自身。最后在afterPropertiesSet,会拿到SqlSessionFactory.getConfiguration(),调用addMapper(Class type)添加到mybatis中

    为什么增加了@MapperScan注解,就能扫描注册Mapper了呢。从MapperScan类可以看到,上面有一个@Import注解,import了MapperScannerRegistrar

    @Import(MapperScannerRegistrar.class)
    @Repeatable(MapperScans.class)
    public @interface MapperScan {
        ...
    }
    

    spring的@Import注解一般用来引用其他的Configuration,还可以引用 ImportSelector和ImportBeanDefinitionRegistrar 实现或其他的Component类。

    Provides functionality equivalent to the element in Spring XML. Allows for importing @Configuration classes, ImportSelector and ImportBeanDefinitionRegistrar implementations, as well as regular component classes (as of 4.2; analogous to AnnotationConfigApplicationContext.register).

    总之,等价于声明了一个MapperScannerRegistrar Bean。我们看一下MapperScannerRegistrar的实现,MapperScannerRegistrat实现了ImportBeanDefinitionRegistrar和ResourceLoaderAware接口。

    ImportBeanDefinitionRegistrar接口,用来在处理@Configuration类的时候,创建bean definition级别的bean。

    Interface to be implemented by types that register additional bean definitions when processing @Configuration classes. Useful when operating at the bean definition level (as opposed to @Bean method/instance level) is desired or necessary.

    在spring的refresh阶段,有一步是invokeBeanFactoryPostProcessors,会调用到ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry,最终会调用到loadBeanDefinitionsFromRegistrars,调用到MapperScannerRegistrar.registerBeanDefinitions

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    	int registryId = System.identityHashCode(registry);
    	if (this.registriesPostProcessed.contains(registryId)) {
    		throw new IllegalStateException(
    				"postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
    	}
    	if (this.factoriesPostProcessed.contains(registryId)) {
    		throw new IllegalStateException(
    				"postProcessBeanFactory already called on this post-processor against " + registry);
    	}
    	this.registriesPostProcessed.add(registryId);
    
    	processConfigBeanDefinitions(registry);
    }
    private void loadBeanDefinitionsForConfigurationClass(
    			ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
    
    	if (trackedConditionEvaluator.shouldSkip(configClass)) {
    		String beanName = configClass.getBeanName();
    		if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
    			this.registry.removeBeanDefinition(beanName);
    		}
    		this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
    		return;
    	}
    
    	if (configClass.isImported()) {
    		registerBeanDefinitionForImportedConfigurationClass(configClass);
    	}
    	for (BeanMethod beanMethod : configClass.getBeanMethods()) {
    		loadBeanDefinitionsForBeanMethod(beanMethod);
    	}
    
    	loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
    	loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
    }
    private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
    	registrars.forEach((registrar, metadata) ->
    			registrar.registerBeanDefinitions(metadata, this.registry));
    }
    

    再看一下MapperScannerRegistrar的实现。registerBeanDefinitions创建了一个BeanDefinition,bean是MapperScannerConfigurer,配置了MapperScannerConfigurer需要的属性配置(配置来源于@MapperScan注解),例如annotationClass, factoryBean等。

    public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
    
      @Override
      public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        AnnotationAttributes mapperScanAttrs = AnnotationAttributes
            .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
        if (mapperScanAttrs != null) {
          registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
              generateBaseBeanName(importingClassMetadata, 0));
        }
      }
    
      void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
          BeanDefinitionRegistry registry, String beanName) {
    
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
        builder.addPropertyValue("processPropertyPlaceHolders", true);
    
        Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
        if (!Annotation.class.equals(annotationClass)) {
          builder.addPropertyValue("annotationClass", annotationClass);
        }
    
        Class<?> markerInterface = annoAttrs.getClass("markerInterface");
        if (!Class.class.equals(markerInterface)) {
          builder.addPropertyValue("markerInterface", markerInterface);
        }
    
        Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
        if (!BeanNameGenerator.class.equals(generatorClass)) {
          builder.addPropertyValue("nameGenerator", BeanUtils.instantiateClass(generatorClass));
        }
    
        Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
        if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
          builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);
        }
    
        String sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef");
        if (StringUtils.hasText(sqlSessionTemplateRef)) {
          builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef"));
        }
    
        String sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef");
        if (StringUtils.hasText(sqlSessionFactoryRef)) {
          builder.addPropertyValue("sqlSessionFactoryBeanName", annoAttrs.getString("sqlSessionFactoryRef"));
        }
    
        List<String> basePackages = new ArrayList<>();
        basePackages.addAll(
            Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));
    
        basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText)
            .collect(Collectors.toList()));
    
        basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName)
            .collect(Collectors.toList()));
    
        if (basePackages.isEmpty()) {
          basePackages.add(getDefaultBasePackage(annoMeta));
        }
    
        String lazyInitialization = annoAttrs.getString("lazyInitialization");
        if (StringUtils.hasText(lazyInitialization)) {
          builder.addPropertyValue("lazyInitialization", lazyInitialization);
        }
    
        String defaultScope = annoAttrs.getString("defaultScope");
        if (!AbstractBeanDefinition.SCOPE_DEFAULT.equals(defaultScope)) {
          builder.addPropertyValue("defaultScope", defaultScope);
        }
    
        builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
    
        registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
    
      }
    
      private static String generateBaseBeanName(AnnotationMetadata importingClassMetadata, int index) {
        return importingClassMetadata.getClassName() + "#" + MapperScannerRegistrar.class.getSimpleName() + "#" + index;
      }
    
      private static String getDefaultBasePackage(AnnotationMetadata importingClassMetadata) {
        return ClassUtils.getPackageName(importingClassMetadata.getClassName());
      }
    
    }
    

    相当于向BeanFactory中注册了一个MapperScannerConfigurer bean。 MapperScannerConfigurer 实现了BeanDefinitionRegistryPostProcessor、InitializingBean、ApplicationContextAware、BeanNameAware等接口

    MapperScannerConfigurer在postProcessBeanDefinitionRegistry中创建了一个ClassPathMapperScanner,并且调用scan方法。 postProcessBeanDefinitionRegistry会在ApplicationContext.refresh阶段调用到,beanFactoryPostProcess之后、registerBeanPostProcessor之前。

    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));
        }
        if (StringUtils.hasText(defaultScope)) {
          scanner.setDefaultScope(defaultScope);
        }
        scanner.registerFilters();
        scanner.scan(
            StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
    }
    

    doScan方法会扫描当前classpath下所有的类,按照@annotation要求、package要求过滤出来,构建成BeanDefinitionHolder,scan的过程中,会注册BeanDefinition。 然后对所有的BeanDefinitionHolder,调用processBeanDefinition,配置beanDefinition的beanClass、属性等信息。

    @Override
    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 {
        processBeanDefinitions(beanDefinitions);
      }
    
      return beanDefinitions;
    }
    

    processBeanDefinitions实现

    String beanClassName = definition.getBeanClassName();
    definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
    definition.setBeanClass(this.mapperFactoryBeanClass);
    ...
    

    beanClass是MapperFactoryBean,也就是bean创建由MapperFactoryBean.getObject生成。

    @Override
    public T getObject() throws Exception {
        return getSqlSession().getMapper(this.mapperInterface);
    }
    public SqlSession getSqlSession() {
        return this.sqlSessionTemplate;
    }
    

    MapperFactoryBean继承了DaoSupport,会在afterPropertiesSet的时候调用checkDaoConfig(),MapperFactoryBean就是在这个时候把Mapper注册到SqlSessionFactory的Configuration中的。

    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();
          }
        }
    }
    

    mybatis中默认的sqlsession是不支持多线程使用的,因为内部的connection不支持多线程使用。为什么spring里的getSeqlSession返回的实例确实线程安全的呢? 下面SqlSessionTemplate对sqlSesssion管理部分会进行讲解。

    mybatis-spring的SqlSessionTemplate如何管理sqlSession的

    SqlSessionTempalte是mybatis spring提供的线程安全的spring管理的sqlSession,并保证sqlSession和spring事务关联。 从SqlSessionTemplate的实现可以看到把sqlSession的请求都转发给了sqlSessionProxy字段,从名字能看到又是Java接口Proxy代理模式。

    public class SqlSessionTemplate implements SqlSession, DisposableBean {
    
      public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
          PersistenceExceptionTranslator exceptionTranslator) {
        this.sqlSessionFactory = sqlSessionFactory;
        this.executorType = executorType;
        this.exceptionTranslator = exceptionTranslator;
        this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
            new Class[] { SqlSession.class }, new SqlSessionInterceptor());
      }
    ...
      @Override
      public void select(String statement, Object parameter, ResultHandler handler) {
        this.sqlSessionProxy.select(statement, parameter, handler);
      }
    }
    

    代理实现在SqlSessionInterceptor,proxy接收到请求后,首先会获取当前的SqlSession,如果当前在一个事务中,那么就使用事务上下文(spring的threadlocal)中的sqlSession。否则开启一个新的sqlSqlSession并放到事务的threadlocal中。请求完成后,调用commit、close

    private class SqlSessionInterceptor implements InvocationHandler {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
          SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
              SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
          try {
            Object result = method.invoke(sqlSession, args);
            if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
              // force commit even on non-dirty sessions because some databases require
              // a commit/rollback before calling close()
              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) {
              closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
            }
          }
        }
    
    
      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;
        }
    
        LOGGER.debug(() -> "Creating a new SqlSession");
        session = sessionFactory.openSession(executorType);
    
        registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
    
        return session;
      }
    }
    

    本文使用 文章同步助手 同步

    相关文章

      网友评论

          本文标题:mybatis spring介绍、使用、实现原理

          本文链接:https://www.haomeiwen.com/subject/bjsgfrtx.html