【Mybatis】Mybatis 是如何整合到 SpringBoot 中的呢?
1 前言
当你把 Spring、SpringBoot、Mybatis 或者 Mybatis-Plus 的源码都看过后,那有没有想过比如 Mybatis 如何整合到 Spring 或者 SpringBoot 的呢?就是思考框架跟框架之间的融合,那么这节我们就来看看单纯的 Mybatis 是如何融合到 SpringBoot 的。融合 Spring 的就不看了,毕竟大家现在用的少,比较老的了,就不写了哈,主要看融合 SpringBoot 的。
SpringBoot 的一大特点就是自动装配,就是 Starter。那么 Mybatis 的融合也是基于此。
2 环境准备
首先我们新建个工程,然后引入 Mybatis,这里我直接截图,就不罗嗦了哈:
SpringBoot :2.3.7.RELEASE
<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.0</version> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>42.2.7</version> </dependency>
配置信息:
# 服务端口
server.port = 9900
# 驱动
spring.datasource.driver-class-name = org.postgresql.Driver
spring.datasource.hikari.connection-test-query=SELECT 1
# 数据库信息
spring.datasource.url = jdbc:postgresql://localhost:5432/test
spring.datasource.username = postgres
spring.datasource.password = sql626..
# 扫描 Mapper xml
mybatis.mapper-locations=classpath*:mapper/*.xml
# SQL日志
mybatis.configuration.log-impl = org.apache.ibatis.logging.stdout.StdOutImpl
启动类以及文件结构:
效果:
3 源码分析
好了,环境准备好了,我们就看看 Mybatis 是如何结合的,从哪里看起呢?我们是不是引入了一个 Starter,自动装配就从 Starter 看起:
3.1 MybatisAutoConfiguration 配置
可以看到 META-INF 下的 spring.factories:
Mybatis 最重要的几个类 Configuration、SqlSessionFactory就都在这里边了:
@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; ... }
3.2 MybatisProperties 属性
mybatis 前缀的属性都会封装进该对象:
@ConfigurationProperties( prefix = "mybatis" ) public class MybatisProperties { public static final String MYBATIS_PREFIX = "mybatis"; ... @NestedConfigurationProperty private Configuration configuration; }
那么该对象是什么时候生成的呢?谁引进的呢?细心的会发现刚才在第一步里 MybatisAutoConfiguration 的构造函数里,依靠 Spring 的依赖注入(构造器注入)来创建的。
小细节:@NestedConfigurationProperty 属性嵌套,构造 Configuration
3.3 SqlSessionFactoryBean
我们再看一个重要的 SqlSessionFactoryBean,看后缀就能知道它是 FactoryBean 形式创建的:
@Bean @ConditionalOnMissingBean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { SqlSessionFactoryBean factory = new SqlSessionFactoryBean(); // 数据源 factory.setDataSource(dataSource); factory.setVfs(SpringBootVFS.class); if (StringUtils.hasText(this.properties.getConfigLocation())) { factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation())); } // configuration 配置对象(上一步的 MybatisProperties 中有的话就用它的,没有的话就创新一个新的) this.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); } if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) { factory.setMapperLocations(this.properties.resolveMapperLocations()); } Set<String> factoryPropertyNames = (Set)Stream.of((new BeanWrapperImpl(SqlSessionFactoryBean.class)).getPropertyDescriptors()).map(FeatureDescriptor::getName).collect(Co Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver(); if (factoryPropertyNames.contains("scriptingLanguageDrivers") && !ObjectUtils.isEmpty(this.languageDrivers)) { factory.setScriptingLanguageDrivers(this.languageDrivers); if (defaultLanguageDriver == null && this.languageDrivers.length == 1) { defaultLanguageDriver = this.languageDrivers[0].getClass(); } } if (factoryPropertyNames.contains("defaultScriptingLanguageDriver")) { factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver); } // getObject return factory.getObject(); }
private void applyConfiguration(SqlSessionFactoryBean factory) { // 先从 MybatisProperties 中拿 org.apache.ibatis.session.Configuration configuration = this.properties.getConfiguration(); // 发现没有或者也没配置的话 就创建一个新的 if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) { configuration = new org.apache.ibatis.session.Configuration(); } if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) { Iterator var3 = this.configurationCustomizers.iterator(); while(var3.hasNext()) { ConfigurationCustomizer customizer = (ConfigurationCustomizer)var3.next(); customizer.customize(configuration); } } factory.setConfiguration(configuration); }
然后继续看 getObject() 方法:
public SqlSessionFactory getObject() throws Exception { // afterPropertiesSet 进行创建 if (this.sqlSessionFactory == null) { this.afterPropertiesSet(); } return this.sqlSessionFactory; }
public void afterPropertiesSet() throws Exception { // 必须校验 Assert.notNull(this.dataSource, "Property 'dataSource' is required"); Assert.notNull(this.sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required"); Assert.state(this.configuration == null && this.configLocation == null || this.configuration == null || this.configLocation == null, "Property 'configuration' and 'configLocation' can not specified with together"); // 创建 this.sqlSessionFactory = this.buildSqlSessionFactory(); }
继续进入 buildSqlSessionFactory 开始创建:
protected SqlSessionFactory buildSqlSessionFactory() throws Exception { XMLConfigBuilder xmlConfigBuilder = null; Configuration targetConfiguration; Optional var10000; 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(), (String)null, this.configurationProperties); targetConfiguration = xmlConfigBuilder.getConfiguration(); } else { LOGGER.debug(() -> { return "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration"; }); targetConfiguration = new Configuration(); var10000 = Optional.ofNullable(this.configurationProperties); Objects.requireNonNull(targetConfiguration); var10000.ifPresent(targetConfiguration::setVariables); } var10000 = Optional.ofNullable(this.objectFactory); Objects.requireNonNull(targetConfiguration); var10000.ifPresent(targetConfiguration::setObjectFactory); var10000 = Optional.ofNullable(this.objectWrapperFactory); Objects.requireNonNull(targetConfiguration); var10000.ifPresent(targetConfiguration::setObjectWrapperFactory); var10000 = Optional.ofNullable(this.vfs); Objects.requireNonNull(targetConfiguration); var10000.ifPresent(targetConfiguration::setVfsImpl); Stream var24; if (StringUtils.hasLength(this.typeAliasesPackage)) { var24 = this.scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream().filter((clazz) -> { return !clazz.isAnonymousClass(); }).filter((clazz) -> { return !clazz.isInterface(); }).filter((clazz) -> { return !clazz.isMemberClass(); }); TypeAliasRegistry var10001 = targetConfiguration.getTypeAliasRegistry(); Objects.requireNonNull(var10001); var24.forEach(var10001::registerAlias); } if (!ObjectUtils.isEmpty(this.typeAliases)) { Stream.of(this.typeAliases).forEach((typeAlias) -> { targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias); LOGGER.debug(() -> { return "Registered type alias: '" + typeAlias + "'"; }); }); } if (!ObjectUtils.isEmpty(this.plugins)) { Stream.of(this.plugins).forEach((plugin) -> { targetConfiguration.addInterceptor(plugin); LOGGER.debug(() -> { return "Registered plugin: '" + plugin + "'"; }); }); } if (StringUtils.hasLength(this.typeHandlersPackage)) { var24 = this.scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter((clazz) -> { return !clazz.isAnonymousClass(); }).filter((clazz) -> { return !clazz.isInterface(); }).filter((clazz) -> { return !Modifier.isAbstract(clazz.getModifiers()); }); TypeHandlerRegistry var25 = targetConfiguration.getTypeHandlerRegistry(); Objects.requireNonNull(var25); var24.forEach(var25::register); } if (!ObjectUtils.isEmpty(this.typeHandlers)) { Stream.of(this.typeHandlers).forEach((typeHandler) -> { targetConfiguration.getTypeHandlerRegistry().register(typeHandler); LOGGER.debug(() -> { return "Registered type handler: '" + typeHandler + "'"; }); }); } targetConfiguration.setDefaultEnumTypeHandler(this.defaultEnumTypeHandler); if (!ObjectUtils.isEmpty(this.scriptingLanguageDrivers)) { Stream.of(this.scriptingLanguageDrivers).forEach((languageDriver) -> { targetConfiguration.getLanguageRegistry().register(languageDriver); LOGGER.debug(() -> { return "Registered scripting language driver: '" + languageDriver + "'"; }); }); } var10000 = Optional.ofNullable(this.defaultScriptingLanguageDriver); Objects.requireNonNull(targetConfiguration); var10000.ifPresent(targetConfiguration::setDefaultScriptingLanguage); if (this.databaseIdProvider != null) { try { targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource)); } catch (SQLException var23) { throw new NestedIOException("Failed getting a databaseId", var23); } } var10000 = Optional.ofNullable(this.cache); Objects.requireNonNull(targetConfiguration); var10000.ifPresent(targetConfiguration::addCache); if (xmlConfigBuilder != null) { try { xmlConfigBuilder.parse(); LOGGER.debug(() -> { return "Parsed configuration file: '" + this.configLocation + "'"; }); } catch (Exception var21) { throw new NestedIOException("Failed to parse config resource: " + this.configLocation, var21); } finally { ErrorContext.instance().reset(); } } // 这里对以后理解 Mybatis 和 Spring 的事务两者结合帮助很大 targetConfiguration.setEnvironment(new Environment(this.environment, (TransactionFactory)(this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory), this.dataSource)); // 扫描我们的 Mapper.xml if (this.mapperLocations != null) { if (this.mapperLocations.length == 0) { LOGGER.warn(() -> { return "Property 'mapperLocations' was specified but matching resources are not found."; }); } else { Resource[] var3 = this.mapperLocations; int var4 = var3.length; for(int var5 = 0; var5 < var4; ++var5) { Resource mapperLocation = var3[var5]; if (mapperLocation != null) { try { XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments()); xmlMapperBuilder.parse(); } catch (Exception var19) { throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", var19); } finally { ErrorContext.instance().reset(); } LOGGER.debug(() -> { return "Parsed mapper file: '" + mapperLocation + "'"; }); } } } } else { LOGGER.debug(() -> { return "Property 'mapperLocations' was not specified."; }); } return this.sqlSessionFactoryBuilder.build(targetConfiguration); }
两个重要的:
(1)Environment 里的 TransactionFactory (SpringManagedTransactionFactory 对 Mybatis 和 Spring 事务的结合衔接)
(2)扫描 Mapper.xml 进行解析注入(这个就不细看了哈,之前看过了 xml 的解析,可以看我之前的哈)
3.4 AutoConfiguredMapperScannerRegistrar
看到这里,我们还差什么?是不是就差 Mapper 接口没看到了?
可以看到我的启动类上 @MapperScan 用于扫描我们的 Mapper 接口,但是当我把这个去掉,程序还是能正常运行,这是为什么呢?这就是 MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar 发挥作用了:
并且它跟 @MapperScan 不冲突,当存在 @MapperScan的时候,那就交给 MapperScannerRegistrar 来处理了,没有的话,就交给 MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar 来处理,他会通过 @Mapper 标记来进行处理哈。
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented @Import({MapperScannerRegistrar.class}) @Repeatable(MapperScans.class) public @interface MapperScan { String[] value() default {}; }
具体如何创建代理(JDK 的代理噢),就不看了哈(之前看过了哈)可以参考这篇【Mapper 接口都是怎么注入到 Spring容器中的?https://www.cnblogs.com/kukuxjx/p/17505609.html】。
4 小结
好啦,看到这里就差不多了哈,画个图来简单小结下: