MyBatis初始化
会加载mybatis-config.xml
配置文件、Mapper.xml
映射配置文件以及Mapper接口
中的注解信息,解析后的配置信息会形成相应的对象并保存到Configuration
对象中。
- 解析
mybatis-config.xml
配置文件SqlSessionFactoryBuilder
XMLConfigBuilder
Configuration
- 解析
Mapper.xml
映射文件XMLMapperBuilder::parse()
XMLStatementBuilder::parseStatementNode()
XMLLanguageDriver
SqlSource
MappedStatement
- 解析
Mapper接口
中的注解MapperRegistry
MapperAnnotationBuilder::parse()
解析mybatis-config.xml配置文件
MyBatis 的初始化流程的入口是SqlSessionFactoryBuilder::build(Reader reader, String environment, Properties properties)
方法,具体流程如下:
SqlSessionFactoryBuilder::builder
-> XMLConfigBuilder::parse()
-> XPathParser::parse()
-> 返回XNode
-> 返回Configuration
-> 创建DefaultSqlSessionFactory
org.apache.ibatis.session.SqlSessionFactoryBuilder
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
首先会使用XMLConfigBuilder::parser()
解析mybatis-config.xml
配置文件
- 先解析标签
configuration
内的数据封装成XNode
,configuration
也是MyBatis中最重要的一个标签。 - 根据
XNode
解析mybatis-config.xml
配置文件的各个标签转变为各个对象。
org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration
private void parseConfiguration(XNode root) {
try {
// issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
再基于Configuration
使用SqlSessionFactoryBuilder::build()
生成DefaultSqlSessionFactory
供给后续执行使用。
解析Mapper.xml映射配置文件
首先使用XMLMapperBuilder::parse()
解析Mapper.xml
,具体流程
XMLMapperBuilder::parse()
-> XPathParser::parse()
-> 返回XNode
-> XMLMapperBuilder#configurationElement()
-> buildStatementFromContext()
-> XMLStatementBuilder
-> XMLLanguageDriver::createSqlSource
-> 返回SqlSource
-> XMLStatementBuilder::parseStatementNode
-> builderAssistant.addMappedStatement
-> 返回MappedStatement
-> XMLMapperBuilder::bindMapperForNamespace
通过XPathParser::evalNode
将mapper
标签中内容解析到XNode
org.apache.ibatis.builder.xml.XMLMapperBuilder#parse
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
再由configurationElement()
方法去解析XNode
中的各个标签
- namespace
- parameterMap
- resultMap
- select|insert|update|delete
org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.isEmpty()) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
其中,基于XMLMapperBuilder::buildStatementFromContext()
,遍历XNode
节点们,逐个创建XMLStatementBuilder
对象,然后通过XMLStatementBuilder::parseStatementNode()
进行解析
- parameterType
- resultType
- selectKey等
并会通过LanguageDriver::createSqlSource()
(默认XmlLanguageDriver
)解析动态sql生成SqlSource
使用GenericTokenParser::parser()
负责将 SQL 语句中的 #{}
替换成相应的 ?
占位符,并获取该 ?
占位符对应的的org.apache.ibatis.mapping.ParameterMapping
对象。
还通过MapperBuilderAssistant::addMappedStatement()
生成MappedStatement
。
public void parseStatementNode() {
// 获取id属性,编号
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
// 判断 databaseId 是否匹配
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
// 解析获得各种属性
String nodeName = context.getNode().getNodeName();
// 获得 SQL 对应的 SqlCommandType 枚举值
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// 创建 XMLIncludeTransformer 对象,并替换 <include /> 标签相关的内容
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String lang = context.getStringAttribute("lang");
// 获得 lang 对应的 LanguageDriver 对象
LanguageDriver langDriver = getLanguageDriver(lang);
// 解析 <selectKey /> 标签
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
// 创建 SqlSource 生成动态sql
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
// 获得 statementType 对应的枚举值
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String resultType = context.getStringAttribute("resultType");
// 获得 resultType 对应的类
Class<?> resultTypeClass = resolveClass(resultType);
String resultMap = context.getStringAttribute("resultMap");
String resultSetType = context.getStringAttribute("resultSetType");
// 获得 resultSet 对应的枚举值
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
if (resultSetTypeEnum == null) {
resultSetTypeEnum = configuration.getDefaultResultSetType();
}
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");
// 创建 MappedStatement 对象
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
解析Mapper接口中的注解
当执行完XMLMapperBuilder::configurationElement()
方法后,会调用XMLMapperBuilder::bindMapperForNamespace()
会转换成对接口上注解进行扫描,具体通过MapperRegistry::addMapper()
调用MapperAnnotationBuilder
实现的。
MapperAnnotationBuilder::parse()
是注解构造器,负责解析Mapper
接口上的注解,解析时需要注意避免和XMLMapperBuilder::parse()
方法冲突,重复解析,最终使用parseStatement
解析。
org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#parse
public void parse() {
String resource = type.toString();
//判断当前 Mapper 接口是否应加载过
if (!configuration.isResourceLoaded(resource)) {
// 加载对应的 XML Mapper,注意避免和 XMLMapperBuilder::parse() 方法冲突
loadXmlResource();
// 标记该 Mapper 接口已经加载过
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
// 解析 @CacheNamespace 注解
parseCache();
parseCacheRef();
// 遍历每个方法,解析其上的注解
for (Method method : type.getMethods()) {
if (!canHaveStatement(method)) {
continue;
}
if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
&& method.getAnnotation(ResultMap.class) == null) {
parseResultMap(method);
}
try {
// 执行解析
parseStatement(method);
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
// 解析待定的方法
parsePendingMethods();
}
parseStatement()
和Mapper.xml
解析流程类似
- 通过加载
LanguageDriver
,GenericTokenParser
等为生成SqlSource
动态sql作准备 - 使用
MapperBuilderAssistant::addMappedStatement()
生成注解@mapper
,@CacheNamespace
等的MappedStatement
信息
void parseStatement(Method method) {
// 获取接口参数类型
final Class<?> parameterTypeClass = getParameterType(method);
// 加载语言处理器,默认XmlLanguageDriver
final LanguageDriver languageDriver = getLanguageDriver(method);
getAnnotationWrapper(method, true, statementAnnotationTypes).ifPresent(statementAnnotation -> {
// 生成动态SQL
final SqlSource sqlSource = buildSqlSource(statementAnnotation.getAnnotation(), parameterTypeClass, languageDriver, method);
final SqlCommandType sqlCommandType = statementAnnotation.getSqlCommandType();
final Options options = getAnnotationWrapper(method, false, Options.class).map(x -> (Options)x.getAnnotation()).orElse(null);
final String mappedStatementId = type.getName() + "." + method.getName();
final KeyGenerator keyGenerator;
String keyProperty = null;
String keyColumn = null;
if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
// 如果有 @SelectKey 注解,则进行处理
SelectKey selectKey = getAnnotationWrapper(method, false, SelectKey.class).map(x -> (SelectKey)x.getAnnotation()).orElse(null);
if (selectKey != null) {
keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
keyProperty = selectKey.keyProperty();
} else if (options == null) {
// 如果无 @Options 注解,则根据全局配置处理
keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
} else {
// 如果有 @Options 注解,则使用该注解的配置处理
keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
keyProperty = options.keyProperty();
keyColumn = options.keyColumn();
}
} else {
// 无
keyGenerator = NoKeyGenerator.INSTANCE;
}
Integer fetchSize = null;
Integer timeout = null;
StatementType statementType = StatementType.PREPARED;
ResultSetType resultSetType = configuration.getDefaultResultSetType();
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = !isSelect;
boolean useCache = isSelect;
// 初始化各种属性
if (options != null) {
if (FlushCachePolicy.TRUE.equals(options.flushCache())) {
flushCache = true;
} else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {
flushCache = false;
}
useCache = options.useCache();
fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
timeout = options.timeout() > -1 ? options.timeout() : null;
statementType = options.statementType();
if (options.resultSetType() != ResultSetType.DEFAULT) {
resultSetType = options.resultSetType();
}
}
// 获得 resultMapId 编号字符串
String resultMapId = null;
if (isSelect) {
//如果有 @ResultMap 注解,使用该注解为 resultMapId 属性
ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
if (resultMapAnnotation != null) {
resultMapId = String.join(",", resultMapAnnotation.value());
} else {
// 如果无 @ResultMap 注解,解析其它注解,作为 resultMapId 属性
resultMapId = generateResultMapName(method);
}
}
// 构建 MappedStatement 对象
assistant.addMappedStatement(
mappedStatementId,
sqlSource,
statementType,
sqlCommandType,
fetchSize,
timeout,
// ParameterMapID
null,
parameterTypeClass,
resultMapId,
getReturnType(method),
resultSetType,
flushCache,
useCache,
// TODO gcode issue #577
false,
keyGenerator,
keyProperty,
keyColumn,
statementAnnotation.getDatabaseId(),
languageDriver,
// ResultSets
options != null ? nullOrEmpty(options.resultSets()) : null);
});
}
生成动态SqlSource
首先需要获取langDriver
实现XMLLanguageDriver / RawLanguageDriver
,现在使用默认的XMLLanguageDriver::createSqlSource(configuration, context, parameterTypeClass)
来创建,再使用XMLScriptBuilder::parseScriptNode()
解析生成SqlSource
。
DynamicSqlSource
:动态的 SqlSource 实现类 , 适用于使用了 OGNL 表达式,或者使用了${}
表达式的SQLRawSqlSource
:原始的 SqlSource 实现类 , 适用于仅使用#{}
表达式,或者不使用任何表达式的情况
org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseScriptNode
public SqlSource parseScriptNode() {
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource;
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
分析一下RawSqlSource
。
public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> clazz = parameterType == null ? Object.class : parameterType;
sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>());
}
使用SqlSourceParser::parse()
去解析SQL。
org.apache.ibatis.builder.SqlSourceBuilder#parse
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
String sql;
if (configuration.isShrinkWhitespacesInSql()) {
sql = parser.parse(removeExtraWhitespaces(originalSql));
} else {
sql = parser.parse(originalSql);
}
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
ParameterMappingTokenHandler
是SqlSourceBuilder
的内部私有静态类, ParameterMappingTokenHandler
负责将匹配到的 #{
和 }
对,替换成相应的 ?
占位符,并获取该 ?
占位符对应的 org.apache.ibatis.mapping.ParameterMapping
对象。并基于ParameterMappingTokenHandler
使用GenericTokenParser::parse()
将SQL中的#{}
转化占位符?
,占位符后创建一个StaticSqlSource
返回。
总结
在MyBatis初始化过程中,会加载mybatis-config.xml
配置文件、Mapper.xml映射配置文件
以及Mapper接口中的注解信息
,解析后的配置信息会形成相应的对象并全部保存到Configuration
对象中,并创建DefaultSqlSessionFactory
供SQl执行过程创建出顶层接口SqlSession
供给用户进行操作。