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

2021/11/26

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

Post Directory