MyBatis源码分析(七)MyBatis与Spring的整合原理与源码分析

系列文章索引

MyBatis源码分析(一)MyBatis整体架构分析
MyBatis源码分析(二)SqlSessionFactory的构建及配置文件读取过程
MyBatis源码分析(二、续)SqlSource创建流程,SQL如何解析?如何将#{id}变成?的
MyBatis源码分析(三)SqlSession的执行主流程
MyBatis源码分析(四)插件拦截器的原理及使用
MyBatis源码分析(四、续)MyBatis分页插件拦截器设计与实现
MyBatis源码分析(五)一级缓存与二级缓存的原理
MyBatis源码分析(六)MetaObject工具类的使用与源码分析
MyBatis源码分析(七)MyBatis与Spring的整合原理与源码分析
深入理解JDK动态代理原理,使用javassist动手写一个动态代理框架

写在前面

MyBatis与Spring的整合,依赖于mybatis-spring包。

其中的设计非常的巧妙,并且与Spring完美结合,我们一起来完整分析一下MyBatis是怎么与Spring整合的。

一、SqlSessionFactoryBean配置SqlSessionFactory

1、初识SqlSessionFactoryBean

SqlSessionFactoryBean是MyBatis与Spring整合的关键,它用于创建MyBatis的SqlSessionFactory。

SqlSessionFactoryBean实现了FactoryBean、InitializingBean和ApplicationListener,这都是Spring的核心接口。

public class SqlSessionFactoryBean
    implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {

并且,SqlSessionFactoryBean中包含了所有的MyBatis的配置项,包括但不限于数据源、事务管理工厂、环境、拦截器、TypeHandler、别名、缓存等等。并且提供了这些属性的get和set方法,开发人员可以随心所欲地对这些属性进行配置。

private static final ResourcePatternResolver RESOURCE_PATTERN_RESOLVER = new PathMatchingResourcePatternResolver();
private static final MetadataReaderFactory METADATA_READER_FACTORY = new CachingMetadataReaderFactory();

private Resource configLocation;

private Configuration configuration;

private Resource[] mapperLocations;

private DataSource dataSource;

private TransactionFactory transactionFactory;

private Properties configurationProperties;

private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();

private SqlSessionFactory sqlSessionFactory;

// EnvironmentAware requires spring 3.1
private String environment = SqlSessionFactoryBean.class.getSimpleName();

private boolean failFast;

private Interceptor[] plugins;

private TypeHandler<?>[] typeHandlers;

private String typeHandlersPackage;

private Class<?>[] typeAliases;

private String typeAliasesPackage;

private Class<?> typeAliasesSuperType;

private LanguageDriver[] scriptingLanguageDrivers;

private Class<? extends LanguageDriver> defaultScriptingLanguageDriver;

// issue #19. No default provider.
private DatabaseIdProvider databaseIdProvider;

private Class<? extends VFS> vfs;

private Cache cache;

private ObjectFactory objectFactory;

private ObjectWrapperFactory objectWrapperFactory;

2、实现ApplicationListener

Spring的事件监听器,关于Spring的事件监听器请移步:
Spring事件详解,Spring-Event源码详解,一文搞透Spring事件管理

重写了onApplicationEvent方法:

// org.mybatis.spring.SqlSessionFactoryBean#onApplicationEvent
@Override
public void onApplicationEvent(ApplicationEvent event) {
  if (failFast && event instanceof ContextRefreshedEvent) {
    // fail-fast -> check all statements are completed
    this.sqlSessionFactory.getConfiguration().getMappedStatementNames();
  }
}

主要是做了快速失败语句验证。

3、实现InitializingBean接口

在Spring的Bean生命周期中,一个Bean实现了InitializingBean接口,会在Bean的初始化过程中调用其afterPropertiesSet方法:

// org.mybatis.spring.SqlSessionFactoryBean#afterPropertiesSet
@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");

  // 关键方法!构造SqlSessionFactory
  this.sqlSessionFactory = buildSqlSessionFactory();
}

4、实现FactoryBean接口

FactoryBean接口是Spring-bean的创建工厂,SpringIOC的第三级缓存中存储的就是FactoryBean,其getObject方法默认就会公开单例实例或创建一个新的原型实例,以下是Spring中FactoryBean的默认实现:

// org.springframework.beans.factory.config.AbstractFactoryBean#getObject
@Override
public final T getObject() throws Exception {
	if (isSingleton()) {
		return (this.initialized ? this.singletonInstance : getEarlySingletonInstance());
	}
	else {
		return createInstance();
	}
}

而此处的SqlSessionFactoryBean重写了getObject方法,直接返回一个SqlSessionFactory(若没有,走创建逻辑)。相当于getObject逻辑不需要Spring来掌控,交给自己来做,生成一个SqlSessionFactory的实例。

// org.mybatis.spring.SqlSessionFactoryBean#getObject
@Override
public SqlSessionFactory getObject() throws Exception {
  if (this.sqlSessionFactory == null) {
    afterPropertiesSet();
  }

  return this.sqlSessionFactory;
}

5、构建SqlSessionFactory

在buildSqlSessionFactory方法中,包含着对SqlSessionFactory的创建逻辑:

// 
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {

  final Configuration targetConfiguration;
  // 创建一个MyBatis的Configuration
  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);
  }

  Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
  Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
  Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);

  if (hasLength(this.typeAliasesPackage)) {
    scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream()
        .filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface())
        .filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
  }

  if (!isEmpty(this.typeAliases)) {
    Stream.of(this.typeAliases).forEach(typeAlias -> {
      targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
      LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
    });
  }

  if (!isEmpty(this.plugins)) {
    Stream.of(this.plugins).forEach(plugin -> {
      targetConfiguration.addInterceptor(plugin);
      LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
    });
  }

  if (hasLength(this.typeHandlersPackage)) {
    scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass())
        .filter(clazz -> !clazz.isInterface()).filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))
        .forEach(targetConfiguration.getTypeHandlerRegistry()::register);
  }

  if (!isEmpty(this.typeHandlers)) {
    Stream.of(this.typeHandlers).forEach(typeHandler -> {
      targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
      LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
    });
  }

  if (!isEmpty(this.scriptingLanguageDrivers)) {
    Stream.of(this.scriptingLanguageDrivers).forEach(languageDriver -> {
      targetConfiguration.getLanguageRegistry().register(languageDriver);
      LOGGER.debug(() -> "Registered scripting language driver: '" + languageDriver + "'");
    });
  }
  Optional.ofNullable(this.defaultScriptingLanguageDriver)
      .ifPresent(targetConfiguration::setDefaultScriptingLanguage);

  if (this.databaseIdProvider != null) {// fix #64 set databaseId before parse mapper xmls
    try {
      targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
    } catch (SQLException e) {
      throw new NestedIOException("Failed getting a databaseId", e);
    }
  }

  Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);

  if (xmlConfigBuilder != null) {
    try {
      xmlConfigBuilder.parse();
      LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");
    } catch (Exception ex) {
      throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
    } finally {
      ErrorContext.instance().reset();
    }
  }

  targetConfiguration.setEnvironment(new Environment(this.environment,
      this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
      this.dataSource));

  if (this.mapperLocations != null) {
    if (this.mapperLocations.length == 0) {
      LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
    } else {
      for (Resource mapperLocation : this.mapperLocations) {
        if (mapperLocation == null) {
          continue;
        }
        try {
          XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
              targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
          xmlMapperBuilder.parse();
        } catch (Exception e) {
          throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
        } finally {
          ErrorContext.instance().reset();
        }
        LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
      }
    }
  } else {
    LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
  }

  return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}

我们可以看出,会将当前SqlSessionFactoryBean中的所有用户加载的配置,都在这里加载到MyBatis的Configuration中,并且调用sqlSessionFactoryBuilder的build方法进行构建SqlSessionFactory,而这个操作也正是MyBatis的标准构建SqlSessionFactoryAPI。

二、SqlSessionTemplate

1、初始SqlSessionTemplate

MyBatis为了与Spring整合,提供了一个SqlSessionTemplate来管理SqlSession,通过代理SqlSession的方式将其与SqlSession进行关联:

public class SqlSessionTemplate implements SqlSession, DisposableBean {

  private final SqlSessionFactory sqlSessionFactory;

  private final ExecutorType executorType;

  private final SqlSession sqlSessionProxy;

  private final PersistenceExceptionTranslator exceptionTranslator;

SqlSessionTemplate 实现了SqlSession,并重写了SqlSession的接口,而SqlSession的这些接口,全都是通过sqlSessionProxy属性进行代理:

@Override
public <E> List<E> selectList(String statement) {
  return this.sqlSessionProxy.selectList(statement);
}

/**
 * {@inheritDoc}
 */
@Override
public <E> List<E> selectList(String statement, Object parameter) {
  return this.sqlSessionProxy.selectList(statement, parameter);
}

/**
 * {@inheritDoc}
 */
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  return this.sqlSessionProxy.selectList(statement, parameter, rowBounds);
}

// 等等都是这样处理的

2、SqlSessionTemplate的构造方法

在创建SqlSessionTemplate时,会执行该构造方法:

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

最终通过JDK动态代理的方式,创建SqlSession的代理,并且创建一个SqlSessionInterceptor处理器。

3、SqlSessionInterceptor执行核心方法

SqlSessionInterceptor 实现了InvocationHandler,也就是说调用SqlSession的方法时,会执行SqlSessionInterceptor 的invoke方法:

private class SqlSessionInterceptor implements InvocationHandler {
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // 获取SqlSession
    SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
        SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
    try {
      // 执行核心方法(select、update、delete等)
      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);
      }
    }
  }
}

(1)获取SqlSession

获取SqlSession的主逻辑和MyBatis的API没什么不同,只不过此处会通过TransactionSynchronizationManager来缓存SqlSession。

// org.mybatis.spring.SqlSessionUtils#getSqlSession(org.apache.ibatis.session.SqlSessionFactory, org.apache.ibatis.session.ExecutorType, org.springframework.dao.support.PersistenceExceptionTranslator)
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
    PersistenceExceptionTranslator exceptionTranslator) {

  notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
  notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
  // ThreadLocal的线程持有者,从事务管理器中获取
  SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

  SqlSession session = sessionHolder(executorType, holder);
  if (session != null) {
    return session;
  }

  // 如果没有,创建一个新的SqlSession
  LOGGER.debug(() -> "Creating a new SqlSession");
  session = sessionFactory.openSession(executorType);

  // 存储SqlSession到ThreadLocal
  registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

  return session;
}

TransactionSynchronizationManager的getResource方法,最终的resource是一个ThreadLocal,我们可以得出结论,同一个线程获取的SqlSession是同一个,不同线程获取的SqlSession是不同的:

private static final ThreadLocal<Map<Object, Object>> resources =
			new NamedThreadLocal<>("Transactional resources");

// org.springframework.transaction.support.TransactionSynchronizationManager#doGetResource
@Nullable
private static Object doGetResource(Object actualKey) {
	Map<Object, Object> map = resources.get();
	if (map == null) {
		return null;
	}
	Object value = map.get(actualKey);
	// Transparently remove ResourceHolder that was marked as void...
	if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
		map.remove(actualKey);
		// Remove entire ThreadLocal if empty...
		if (map.isEmpty()) {
			resources.remove();
		}
		value = null;
	}
	return value;
}

在registerSessionHolder逻辑中,只有开启了事务、并且事务工厂是SpringManagedTransactionFactory,才会缓存SqlSession:

// org.mybatis.spring.SqlSessionUtils#registerSessionHolder
private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,
    PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
  SqlSessionHolder holder;
  // 如果当前线程的事务同步是活动的,则返回。
  if (TransactionSynchronizationManager.isSynchronizationActive()) {
    Environment environment = sessionFactory.getConfiguration().getEnvironment();

    if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
      LOGGER.debug(() -> "Registering transaction synchronization for SqlSession [" + session + "]");

      holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
      TransactionSynchronizationManager.bindResource(sessionFactory, holder);
      TransactionSynchronizationManager
          .registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
      holder.setSynchronizedWithTransaction(true);
      holder.requested();
    } else {
      if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) {
        LOGGER.debug(() -> "SqlSession [" + session
            + "] was not registered for synchronization because DataSource is not transactional");
      } else {
        throw new TransientDataAccessResourceException(
            "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");
      }
    }
  } else {
    LOGGER.debug(() -> "SqlSession [" + session
        + "] was not registered for synchronization because synchronization is not active");
  }

}

(2)关闭SqlSession

关闭SqlSession同样会先从ThreadLocal中释放,然后调用SqlSession的close方法

// org.mybatis.spring.SqlSessionUtils#closeSqlSession
public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
  notNull(session, NO_SQL_SESSION_SPECIFIED);
  notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);

  SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
  if ((holder != null) && (holder.getSqlSession() == session)) {
    LOGGER.debug(() -> "Releasing transactional SqlSession [" + session + "]");
    holder.released();
  } else {
    LOGGER.debug(() -> "Closing non transactional SqlSession [" + session + "]");
    session.close();
  }
}

4、总结

也就是说,通过代理SqlSession,每次执行SqlSession的select、update等方法时,会先执行获取SqlSession的方法,然后再调用select方法。

也就是说,我们做的sqlSessionFactory.openSession这个操作,交给框架的代理做了。

5、一级缓存失效问题

Mybatis中的一级缓存是基于SqlSession来实现的,所以在执行同一个sql时,如果使用的是同一个SqlSession对象,那么就能利用到一级缓存,提高sql的执行效率。

但是在Spring整合Mybatis后,如果没有执行某个方法时,该方法上没有加@Transactional注解,也就是没有开启Spring事务,那么后面在执行具体sql时,没执行一个sql时都会新生成一个SqlSession对象来执行该sql,这就是我们说的一级缓存失效(也就是没有使用同一个SqlSession对象),而如果开启了Spring事务,那么该Spring事务中的多个sql,在执行时会使用同一个SqlSession对象,从而一级缓存生效。

个人理解:实际上Spring整合Mybatis后一级缓存失效并不是问题,是正常的实现,因为,一个方法如果没有开启Spring事务,那么在执行sql时候,那就是每个sql单独一个事务来执行,也就是单独一个SqlSession对象来执行该sql,如果开启了Spring事务,那就是多个sql属于同一个事务,那自然就应该用一个SqlSession来执行这多个sql。所以,在没有开启Spring事务的时候,SqlSession的一级缓存并不是失效了,而是存在的生命周期太短了(执行完一个sql后就被销毁了,下一个sql执行时又是一个新的SqlSession了)。

三、@MapperScan扫描注册的Mapper

1、@MapperScan

@MapperScan用于扫描Mapper所在的包,注册了一个MapperScannerRegistrar

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {

2、MapperScannerRegistrar

MapperScannerRegistrar实现了ImportBeanDefinitionRegistrar,在registerBeanDefinitions方法中,进行了MapperScannerConfigurer的注册:

// org.mybatis.spring.annotation.MapperScannerRegistrar#registerBeanDefinitions(org.springframework.core.type.AnnotationMetadata, org.springframework.beans.factory.support.BeanDefinitionRegistry)
@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) {

  // 定义一个MapperScannerConfigurer的bean,并且设置许多参数
  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);
  }

  builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));

  registry.registerBeanDefinition(beanName, builder.getBeanDefinition());

}

3、MapperScannerConfigurer

MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,在其postProcessBeanDefinitionRegistry方法中,创建了ClassPathMapperScanner 进行了扫描和注册:

// org.mybatis.spring.mapper.MapperScannerConfigurer#postProcessBeanDefinitionRegistry
@Override
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));
  }
  scanner.registerFilters();
  scanner.scan( // 扫描注册过程
      StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
// org.springframework.context.annotation.ClassPathBeanDefinitionScanner#scan
public int scan(String... basePackages) {
	int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
    // 扫描注册
	doScan(basePackages);

	// Register annotation config processors, if necessary.
	if (this.includeAnnotationConfig) {
		AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
	}

	return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
// org.mybatis.spring.mapper.ClassPathMapperScanner#doScan
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
  // Spring的扫描+Bean的注册
  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 {
    // Mapper的最终属性调整过程
    processBeanDefinitions(beanDefinitions);
  }

  return beanDefinitions;
}

(1)processBeanDefinitions

在processBeanDefinitions方法中,对BeanDefinition进行属性调整,关键逻辑-设置Bean的Class为MapperFactoryBean,因为Mapper都是接口无法实例化,所以只能通过MapperFactoryBean来进行实例化。

private Class<? extends MapperFactoryBean> mapperFactoryBeanClass = MapperFactoryBean.class;

// org.mybatis.spring.mapper.ClassPathMapperScanner#processBeanDefinitions
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
  GenericBeanDefinition definition;
  for (BeanDefinitionHolder holder : beanDefinitions) {
    definition = (GenericBeanDefinition) holder.getBeanDefinition();
    String beanClassName = definition.getBeanClassName();
    LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
        + "' mapperInterface");

    // the mapper interface is the original class of the bean
    // but, the actual class of the bean is MapperFactoryBean
    definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
    definition.setBeanClass(this.mapperFactoryBeanClass); // 设置Bean的Class

    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) {
      LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
      definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
    }
    definition.setLazyInit(lazyInitialization);
  }
}

4、MapperFactoryBean

MapperFactoryBean实现了FactoryBean,同样重写了getObject方法:

// org.mybatis.spring.mapper.MapperFactoryBean#getObject
@Override
public T getObject() throws Exception {
  // 
  return getSqlSession().getMapper(this.mapperInterface);
}

看到这,我们是不是眼前一亮!

正是这里,获取了SqlSession,并且调用了getMapper方法获取了这个接口!

这个Mapper接口注册到Spring容器中,并通过这种方式获取。

5、拓展:注册MapperScannerConfigurer而不用@MapperScan

通过@MapperScan导入了MapperScannerRegistrar类
MapperScannerRegistrar类实现了ImportBeanDefinitionRegistrar接口,所以Spring在启动时会调用MapperScannerRegistrar类中的registerBeanDefinitions方法
在registerBeanDefinitions方法中注册一个MapperScannerConfigurer类型的BeanDefinition
而MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,所以Spring在启动过程中时会调用它的postProcessBeanDefinitionRegistry()方法
在postProcessBeanDefinitionRegistry方法中会生成一个ClassPathMapperScanner对象,然后进行扫描

可以不使用@MapperScan注解,而可以直接定义一个Bean,比如:

@Bean
public MapperScannerConfigurer mapperScannerConfigurer() {
 MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
 mapperScannerConfigurer.setBasePackage("com.text");
 return mapperScannerConfigurer;
}

四、总结

到此,MyBatis与Spring的整合全流程,我们已经彻底分析完毕了。

是不是让人感叹,学好源码真不容易,看大佬写的框架真实太精彩了!

1、总结

简单总结一下:
使用@MapperScan会进行Mapper的扫描。

MyBatis整合Spring的核心就是SqlSessionTemplate,因为MyBatis自带的DefaultSqlSession是线程不安全的,所以使用SqlSessionTemplate中间层做了一个代理,SqlSessionTemplate中有一个属性就是DefaultSqlSession的代理,每次调用SqlSessionTemplate的selectOne等方法时,其实就是调用的DefaultSqlSession的代理的selectOne方法。sessionFactory.openSession方法就是在此处执行的。

也就是说,相当于每次执行增删改查的时候,选用的SqlSession虽然是SqlSessionTemplate,但是实际上是调用的DefaultSqlSession的,而在代理方法中,每次都会执行sessionFactory.openSession方法(如果事务不生效的话)。

2、Spring整合Mybatis之后SQL执行流程

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秃了也弱了。

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值