-
目的
本文本来想通过探究下spring集成mybatis时,是如何保证mybatis和spring在事务执行时,是如何保证在一个事务执行时,获取到的数据库的连接是同一个的?但是不想,越研究,雪球滚的越大,只要从spring集成mybatis开始探究起,应该会有几篇内容的输出。包括springboot集成mybaits,mybatis mapper动态代理的创建流程,以及springboot事务和mybatis事务的联动,下面是第一篇关于springboot是如何集成mybatis的
-
mybatis-spring-boot-starter
首先mybatis-spring-boot-starter是一个空壳jar, 通过pom文件依赖了
- spring-boot-starter-jdbc
- mybatis-spring-boot-autoconfigure
- mybatis
- mybatis-spring
其中 mybatis-spring-boot-autoconfigure 主要负责配置类的引入(通过@Bean的方式) ,而引入的配置类主要来自于 mybatis-spring ,而 mybatis-spring 其实是主要做了 spring 和 mybatis 之间的适配工作
mybatis-spring-boot-starter#pom.xml如下:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
</dependency>
</dependencies>
-
mybatis-spring-boot-autoconfigure 引入的配置类
-
MybatisAutoConfiguration
这个项目引入的配置类主要集中在 org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration 配置类中,下面是其详细的配置,可以看下其中的详细注释,可以看到
1、通过 @Bean 的方式配置了 SqlSessionFactory - mybatis中的核心组件,用来根据配置创建SqlSession
2、配置了一个 SqlSessionTemplate ,延续了spring的一贯风格,SqlSession的模版,SqlSessionTemplate 实现了SqlSession ,在内部通过组合了SqlSessionFactory 以及通过JDK代理了SqlSession 的所有方法,spring为什么要这么做 ? 为什么要专门创建一个模版类,然后代理 SqlSession 所有的方法?下面也会详细分析下
下面详细讲解下上面两个bean组件自动配置时,其中涉及到的一些比较重要的细节
@org .springframework.context.annotation.Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
public class MybatisAutoConfiguration implements InitializingBean {
private static final Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class);
private final MybatisProperties properties;
private final Interceptor[] interceptors;
private final TypeHandler[] typeHandlers;
private final LanguageDriver[] languageDrivers;
private final ResourceLoader resourceLoader;
private final DatabaseIdProvider databaseIdProvider;
private final List<ConfigurationCustomizer> configurationCustomizers;
private final List<SqlSessionFactoryBeanCustomizer> sqlSessionFactoryBeanCustomizers;
public MybatisAutoConfiguration(MybatisProperties properties, ObjectProvider<Interceptor[]> interceptorsProvider,
ObjectProvider<TypeHandler[]> typeHandlersProvider, ObjectProvider<LanguageDriver[]> languageDriversProvider,
ResourceLoader resourceLoader, ObjectProvider<DatabaseIdProvider> databaseIdProvider,
ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider,
ObjectProvider<List<SqlSessionFactoryBeanCustomizer>> sqlSessionFactoryBeanCustomizers) {
this.properties = properties;
this.interceptors = interceptorsProvider.getIfAvailable();
this.typeHandlers = typeHandlersProvider.getIfAvailable();
this.languageDrivers = languageDriversProvider.getIfAvailable();
this.resourceLoader = resourceLoader;
this.databaseIdProvider = databaseIdProvider.getIfAvailable();
this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();
this.sqlSessionFactoryBeanCustomizers = sqlSessionFactoryBeanCustomizers.getIfAvailable();
}
@Override
public void afterPropertiesSet() {
checkConfigFileExists();
}
private void checkConfigFileExists() {
if (this.properties.isCheckConfigLocation() && StringUtils.hasText(this.properties.getConfigLocation())) {
Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation());
Assert.state(resource.exists(),
"Cannot find config location: " + resource + " (please add config file or check your Mybatis configuration)" );
}
}
// 此处通过SqlSessionFactoryBean构造了一个SqlSessionFactory
@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()));
}
applyConfiguration(factory);
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 (this.properties.getTypeAliasesSuperType() != null) {
factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType());
}
if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
}
if (!ObjectUtils.isEmpty(this.typeHandlers)) {
factory.setTypeHandlers(this.typeHandlers);
}
Resource[] mapperLocations = this.properties.resolveMapperLocations();
if (!ObjectUtils.isEmpty(mapperLocations)) {
factory.setMapperLocations(mapperLocations);
}
Set<String> factoryPropertyNames = Stream
.of(new BeanWrapperImpl(SqlSessionFactoryBean.class).getPropertyDescriptors()).map(PropertyDescriptor::getName)
.collect(Collectors.toSet());
Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();
if (factoryPropertyNames.contains( "scriptingLanguageDrivers" ) && !ObjectUtils.isEmpty(this.languageDrivers)) {
// Need to mybatis-spring 2.0.2+
factory.setScriptingLanguageDrivers(this.languageDrivers);
if (defaultLanguageDriver == null && this.languageDrivers.length == 1) {
defaultLanguageDriver = this.languageDrivers[0].getClass();
}
}
if (factoryPropertyNames.contains( "defaultScriptingLanguageDriver" )) {
// Need to mybatis-spring 2.0.2+
factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver);
}
applySqlSessionFactoryBeanCustomizers(factory);
return factory.getObject();
}
private void applyConfiguration(SqlSessionFactoryBean factory) {
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);
}
private void applySqlSessionFactoryBeanCustomizers(SqlSessionFactoryBean factory) {
if (!CollectionUtils.isEmpty(this.sqlSessionFactoryBeanCustomizers)) {
for (SqlSessionFactoryBeanCustomizer customizer : this.sqlSessionFactoryBeanCustomizers) {
customizer.customize(factory);
}
}
}
//此处配置了一个 SqlSessionTemplate,而SqlSessionTemplate内部组合了SqlSessionFactory
@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);
}
}
//下面这个自动注册其实用不到,
//因为一般通过@MapperScan注解也会import一个具有相同功能的注册器,此处这个只是兜底使用的
/**
* This will just scan the same base package as Spring Boot does. If you want more power, you can explicitly use
* { @link org.mybatis.spring.annotation.MapperScan} but this will get typed mappers working correctly, out-of-the-box,
* similar to using Spring Data JPA repositories.
*/
public static class AutoConfiguredMapperScannerRegistrar
implements BeanFactoryAware, EnvironmentAware, ImportBeanDefinitionRegistrar {
private BeanFactory beanFactory;
private Environment environment;
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
if (!AutoConfigurationPackages.has(this.beanFactory)) {
logger.debug( "Could not determine auto-configuration package, automatic mapper scanning disabled." );
return;
}
logger.debug( "Searching for mappers annotated with @Mapper" );
List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
if (logger.isDebugEnabled()) {
packages.forEach(pkg -> logger.debug( "Using auto-configuration base package '{}'" , pkg));
}
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
builder.addPropertyValue( "processPropertyPlaceHolders" , true);
builder.addPropertyValue( "annotationClass" , Mapper.class);
builder.addPropertyValue( "basePackage" , StringUtils.collectionToCommaDelimitedString(packages));
BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class);
Set<String> propertyNames = Stream.of(beanWrapper.getPropertyDescriptors()).map(PropertyDescriptor::getName)
.collect(Collectors.toSet());
if (propertyNames.contains( "lazyInitialization" )) {
// Need to mybatis-spring 2.0.2+
builder.addPropertyValue( "lazyInitialization" , "${mybatis.lazy-initialization:false}" );
}
if (propertyNames.contains( "defaultScope" )) {
// Need to mybatis-spring 2.0.6+
builder.addPropertyValue( "defaultScope" , "${mybatis.mapper-default-scope:}" );
}
// for spring-native
boolean injectSqlSession = environment.getProperty( "mybatis.inject-sql-session-on-mapper-scan" , Boolean.class,
Boolean.TRUE);
if (injectSqlSession && this.beanFactory instanceof ListableBeanFactory) {
ListableBeanFactory listableBeanFactory = (ListableBeanFactory) this.beanFactory;
Optional<String> sqlSessionTemplateBeanName = Optional
.ofNullable(getBeanNameForType(SqlSessionTemplate.class, listableBeanFactory));
Optional<String> sqlSessionFactoryBeanName = Optional
.ofNullable(getBeanNameForType(SqlSessionFactory.class, listableBeanFactory));
if (sqlSessionTemplateBeanName.isPresent() || !sqlSessionFactoryBeanName.isPresent()) {
builder.addPropertyValue( "sqlSessionTemplateBeanName" ,
sqlSessionTemplateBeanName.orElse( "sqlSessionTemplate" ));
} else {
builder.addPropertyValue( "sqlSessionFactoryBeanName" , sqlSessionFactoryBeanName.get());
}
}
builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
}
@Override
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
private String getBeanNameForType(Class<?> type, ListableBeanFactory factory) {
String[] beanNames = factory.getBeanNamesForType(type);
return beanNames.length > 0 ? beanNames[0] : null;
}
}
/**
* If mapper registering configuration or mapper scanning configuration not present, this configuration allow to scan
* mappers based on the same component-scanning path as Spring Boot itself.
*/
@org .springframework.context.annotation.Configuration
@Import(AutoConfiguredMapperScannerRegistrar.class)
@ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class })
public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
@Override
public void afterPropertiesSet() {
logger.debug(
"Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer." );
}
}
}
-
SqlSessionFactory 的配置中比较重要的点
重点看下 org.mybatis.spring.SqlSessionFactoryBean#buildSqlSessionFactory 方法, 其中 把 DataSource 设置到了 Environment 中,以及把 Environment 设置到了全局的 Configuration 中 ( Environment,以及Configuration 都是mybatis中原生的组件 ) , 这样后续mybaits 的事务控制 以及 datasource 就可以和spring进行联动了
.............
targetConfiguration.setEnvironment(new Environment(this.environment,
this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
this.dataSource));
..............
注入两个基本的组件之后,后续的组件通过 @MapperScan 注解引入的方式进行,下面详细讲解下
-
@MapperScan 注解
可以看到 通过 @Import(MapperScannerRegistrar.class) 注解导入了 MapperScannerRegistrar ,它实现了 ImportBeanDefinitionRegistrar ,下面详细分析下
说明: 实际上spring并没有在内部像支持 @Service 等原生注解一样支持识别 @MapperScan 注解 , 而是spring 通过递归识别到了 @MapperScan 注解上的 @Import 注解 (这个是spring原生内置的能力) ,从而达到识别 @MapperScan 的效果 ,类似于 @EnableXXX 的功效
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
/**
* Alias for the { @link #basePackages()} attribute. Allows for more concise annotation declarations e.g.:
* { @code @MapperScan("org.my.pkg")} instead of { @code @MapperScan(basePackages = "org.my.pkg"})}.
*
* @return base package names
*/
@AliasFor( "basePackages" )
String[] value() default {};
/**
* Base packages to scan for MyBatis interfaces. Note that only interfaces with at least one method will be
* registered; concrete classes will be ignored.
*
* @return base package names for scanning mapper interface
*/
@AliasFor( "value" )
String[] basePackages() default {};
/**
* Type-safe alternative to { @link #basePackages()} for specifying the packages to scan for annotated components. The
* package of each class specified will be scanned.
* <p>
* Consider creating a special no-op marker class or interface in each package that serves no purpose other than being
* referenced by this attribute.
*
* @return classes that indicate base package for scanning mapper interface
*/
Class<?>[] basePackageClasses() default {};
/**
* The { @link BeanNameGenerator} class to be used for naming detected components within the Spring container.
*
* @return the class of { @link BeanNameGenerator}
*/
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
/**
* This property specifies the annotation that the scanner will search for.
* <p>
* The scanner will register all interfaces in the base package that also have the specified annotation.
* <p>
* Note this can be combined with markerInterface.
*
* @return the annotation that the scanner will search for
*/
Class<? extends Annotation> annotationClass() default Annotation.class;
/**
* This property specifies the parent that the scanner will search for.
* <p>
* The scanner will register all interfaces in the base package that also have the specified interface class as a
* parent.
* <p>
* Note this can be combined with annotationClass.
*
* @return the parent that the scanner will search for
*/
Class<?> markerInterface() default Class.class;
/**
* Specifies which { @code SqlSessionTemplate} to use in the case that there is more than one in the spring context.
* Usually this is only needed when you have more than one datasource.
*
* @return the bean name of { @code SqlSessionTemplate}
*/
String sqlSessionTemplateRef() default "" ;
/**
* Specifies which { @code SqlSessionFactory} to use in the case that there is more than one in the spring context.
* Usually this is only needed when you have more than one datasource.
*
* @return the bean name of { @code SqlSessionFactory}
*/
String sqlSessionFactoryRef() default "" ;
/**
* Specifies a custom MapperFactoryBean to return a mybatis proxy as spring bean.
*
* @return the class of { @code MapperFactoryBean}
*/
Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
/**
* Whether enable lazy initialization of mapper bean.
* <p>
* Default is { @code false}.
* </p>
*
* @return set { @code true} to enable lazy initialization
*
* @since 2.0.2
*/
String lazyInitialization() default "" ;
/**
* Specifies the default scope of scanned mappers.
* <p>
* Default is { @code ""} (equiv to singleton).
* </p>
*
* @return the default scope
*/
String defaultScope() default AbstractBeanDefinition.SCOPE_DEFAULT;
}
-
registerBeanDefinitions
@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));
}
}
1、获取到 MapperScan 注解的信息
2、之后进行注册 registerBeanDefinitions , 这个方法主要是向spring 容器中注册了一个名叫 MapperScannerConfigurer 的 BeanDefinition ,这个类非常重要,是一个 BeanDefinition的后置处理器,会在容器启动的时候扫描指定的包路径(mapper路径),然后将满足规则的mapper 构造成 BeanDefinition 再次注册进spring 容器 (也就相当于把mybaits的mapper注册为spring容器中的bean了)
-
MapperScannerConfigurer
看下这个类的签名,其事项了 BeanDefinitionRegistryPostProcessor ,所以会在其 postProcessBeanDefinitionRegistry 方法中进行 BeanDefinition 的注册
public class MapperScannerConfigurer
implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
}
接着通过一个ClassPathMapperScanner 进行package的扫描
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);
}
// 此处会 注册class文件过滤器,这里设置为接收所有的class
scanner.registerFilters();
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
注意下上面的 scanner.registerFilters(); 方法 , 这和方法会根据用户选择注册识别指定规则的class,一般情况下只配置了 @MapperScan( "com.future.common.mapper" ) ,所以相当于spring会把指定包下的所有class当做mapper纳入BeanDefinition的候选范围. 然后重点看下其 scan 方法
public int scan(String... basePackages) {
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
// 重点看下这个 doScan 方法
doScan(basePackages);
// Register annotation config processors, if necessary.
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
doScan 方法
先通过父类的doScan进行查找指定包下的class(会根据之前注册的filter进行过滤) , 之后走processBeanDefinitions 进行添加额外的属性,重点看下 processBeanDefinitions 方法
@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(beanDefinitions)
这个方法 设置了beanClass = MapperFactoryBean.class 而且设置了构造函数的参数为 beanClassNane 也就是实际定义的Mapper的interface class , 同时查看 MapperFactoryBean 也确实有一个带参的构造函数,同时可以看到下面同时也通过属性注入的方式,将mapperInterface 赋值了
同时有一个非常重要的属性设置:definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
正是通过修改自动注入的模式为byType,保证了MapperFactoryBean的bean在创建时。spring会自动进行自动注入,下面进行 MapperFactoryBean 看下其实现的详细细节
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
AbstractBeanDefinition definition;
BeanDefinitionRegistry registry = getRegistry();
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (AbstractBeanDefinition) holder.getBeanDefinition();
boolean scopedProxy = false;
if (ScopedProxyFactoryBean.class.getName().equals(definition.getBeanClassName())) {
definition = (AbstractBeanDefinition) Optional
.ofNullable(((RootBeanDefinition) definition).getDecoratedDefinition())
.map(BeanDefinitionHolder::getBeanDefinition).orElseThrow(() -> new IllegalStateException(
"The target bean definition of scoped proxy bean not found. Root bean definition[" + holder + "]" ));
scopedProxy = true;
}
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
try {
// for spring-native
definition.getPropertyValues().add( "mapperInterface" , Resources.classForName(beanClassName));
} catch (ClassNotFoundException ignore) {
// ignore
}
// 这个地方非常重要,设置
definition.setBeanClass(this.mapperFactoryBeanClass);
definition.getPropertyValues().add( "addToConfig" , this.addToConfig);
// Attribute for MockitoPostProcessor
// https://github.com/mybatis/spring-boot-starter/issues/475
definition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, beanClassName);
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() + "'." );
// 这段代码非常重要,通过设置bean的依赖注入的模式为 AUTOWIRE_BY_TYPE , 可以保证 beanDefinition在创建bean时属性能自动注入
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
definition.setLazyInit(lazyInitialization);
if (scopedProxy) {
continue;
}
if (ConfigurableBeanFactory.SCOPE_SINGLETON.equals(definition.getScope()) && defaultScope != null) {
definition.setScope(defaultScope);
}
if (!definition.isSingleton()) {
BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(holder, registry, true);
if (registry.containsBeanDefinition(proxyHolder.getBeanName())) {
registry.removeBeanDefinition(proxyHolder.getBeanName());
}
registry.registerBeanDefinition(proxyHolder.getBeanName(), proxyHolder.getBeanDefinition());
}
}
}
-
MapperFactoryBean
可以看到 MapperFactoryBean 实现了 FactoryBean ,所以一个 MapperFactoryBean 就是一个MapperInterface的代理,同时其 继承 了 SqlSessionDaoSupport (其实现了InitializingBean接口) ,所以 MapperFactoryBean 在初始化时会执行 afterPropertiesSet 方法,而这个方法里会执行 checkDaoConfig方法,如下
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {}
org.mybatis.spring.mapper.MapperFactoryBean#checkDaoConfig
这个方法会校验 sqlSessionTemplate 是否存在 ,因为 sqlSessionTemplate 已经在MybatisAutoConfiguration中通过@Bean 定义了,而且 MapperFactoryBean 也已经通过 ByType 类型进行了自动注入,所以这里是可以顺利通过校验的, 然后就是走 configuration.addMapper(this.mapperInterface); ,手动把 mapperInterface 添加进原生mybatis 的 configuration 中 (这一步借助了原生了Mybatis的注册mapper接口的能力,底层会对MapperInterface 进行JDK动态代理-下一篇会进行讲解生成动态代理的过程)
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();
}
}
}
接着看 MapperFactoryBean 类中的其他实现了 FactoryBean 的接口
可以看到 代理的class 的类型就是通过构造函数传进来的 mapperInterface , 而且是单例的,所以后续通过spring容器自动注入 Mapper时,实际注入的都是 return getSqlSession().getMapper(this.mapperInterface); 这行代码返回的对象,简单点来说,这里其实就是获取了Mybatis 中MapperInterface的代理对象
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
@Override
public Class<T> getObjectType() {
return this.mapperInterface;
}
@Override
public boolean isSingleton() {
return true;
}
-
总结
最后梳理下mybatis-spring-boot-starter的工作流程,首先在 MybatisAutoConfiguration 自动配置类中配置了
SqlSessionFactory , SqlSessionTemplate 两个基本bean
然后通过 @MapperScan 注解上的 @Import(MapperScannerRegistrar.class) 注解 引入了一个实现 ImportBeanDefinitionRegistrar 接口的 MapperScannerRegistrar 配置类,而在这个配置类中 又向spring容器中注入了一个 实现了 BeanDefinitionRegistryPostProcessor 后置处理器的 MapperScannerConfigurer ,而在其后置处理器中借助 ClassPathMapperScanner 对指定 包下的 bean 进行扫描,然后把 扫描到的Mapper 的class注册为 一个实现了 FactoryBean 的 MapperFactoryBean ,每一个MapperFactoryBean 都代表一个 Mapper 的代理类,通过其getObject方法返回Mapper对应的Mybatis生成的JDK动态代理 , 之后对于 sql 的查询等都还是走mybatis生成的动态代理去走后续具体的流程
以上是本文梳理的部分,在这些大体的流程中还是有一些细节 是值得学习的,包括, 扫描包中类选择 过滤器 ,以及 Bean的自动注入的模式
但是以上的部分并未解答
Spring 创建出来的 SqlSessionTemplate 的作用是什么 ?
以及 configuration.addMapper(this.mapperInterface); 背后做了什么 ?
以及 集成之后,spring的事务和 mybatis的代理中当执行一个事务时是如何保证获取到的是在同一个事务里或者说获取到的数据库的连接是同一个?
对于上面这些问题的解答,会继续进行通过源码的方式来找到答案以及在这个过程中探究spring和mybtis的架构设计