本文主要讲述springboot整合mybatis的自动配置流程。
1. MybatisAutoConfiguration
MybatisAutoConfiguration是一个配置类,主要是注入SqlSessionTemplate,SqlSessionFactory,以及注册@Mapper标注的类。
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
// spring整合mybatis,以SqlSessionFactoryBean替代SqlSessionFactoryBuilder。
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
// 忽略一些默认配置,最后返回一个SqlSessionFactory
.....
return factory.getObject();
}
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
// 未配置ExecutorType,则使用默认的ExecutorType.SIMPLE
ExecutorType executorType = this.properties.getExecutorType();
if (executorType != null) {
return new SqlSessionTemplate(sqlSessionFactory, executorType);
} else {
// spring整合mybatis,以SqlSessionTemplate替代SqlSession
return new SqlSessionTemplate(sqlSessionFactory);
}
}
@org.springframework.context.annotation.Configuration
// 扫描@Mapper标注的类
@Import(AutoConfiguredMapperScannerRegistrar.class)
@ConditionalOnMissingBean({
MapperFactoryBean.class, MapperScannerConfigurer.class })
public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
}
2. SqlSessionFactoryBean
依照上文调用getObject方法,进入SqlSessionFactoryBean类。
public class SqlSessionFactoryBean
implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
public SqlSessionFactory getObject() throws Exception {
// 首次调用sqlSessionFactory==null进入afterPropertiesSet
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
public void afterPropertiesSet() throws Exception {
notNull(dataSource, "Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
// 这里要求configuration和configuration二者只能存在一个
state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
"Property 'configuration' and 'configLocation' can not specified with together");
this.sqlSessionFactory = buildSqlSessionFactory();
}
}
buildSqlSessionFactory 跟SqlSessionFactoryBuilder里面的build逻辑类似。
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
final Configuration targetConfiguration;
XMLConfigBuilder xmlConfigBuilder = null;
// 通常使用springboot默认配置,则configuration != null
if (this.configuration != null) {
targetConfiguration = this.configuration;
...
// 进入这里是自定义了xml配置文件
} else if (this.configLocation != null) {
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
targetConfiguration = xmlConfigBuilder.getConfiguration();
}else {
// 什么都没有配置,则创建一个默认的Configuration
targetConfiguration = new Configuration();
}
// 忽略一些常规配置
....
// 这里设置映射xml文件地址
if (this.mapperLocations != null) {
if (this.mapperLocations.length == 0) {
LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
} else {
// 遍历每个mapper.xml文件
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
// 这里执行具体xml解析操作
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
}
}
} else {
LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
}
return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}
3.XMLMapperBuilder
这里是进行解析xml文件,并将解析好的信息存入Configuration对象。
- parse
public class XMLMapperBuilder extends BaseBuilder {
public void parse() {
// 判断是否解析过
if (!configuration.isResourceLoaded(resource)) {
// 解析里面的<mapper><mapper/>信息,包含select之类的语句解析
configurationElement(parser.evalNode("/mapper"));
// 标记解析过的资源,避免重复解析
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
// 这里是之前解析失败的,再次尝试解析
// 解析ResultMap
parsePendingResultMaps();
// 解析CacheRef
parsePendingCacheRefs();
// 解析Statement
parsePendingStatements();
}
}
- bindMapperForNamespace
这个方法主要是将dao接口类与代理进行关联。
private void bindMapperForNamespace() {
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
}
if (boundType != null && !configuration.hasMapper(boundType)) {
// 这里是标记xml文件对应的dao类,已经解析过了
configuration.addLoadedResource("namespace:" + namespace);
// 这里将dao类绑定对应的代理类
configuration.addMapper(boundType);
}
}
}