共计 12901 个字符,预计需要花费 33 分钟才能阅读完成。
该系列文章收录在公众号【Ccww 技术博客】,原创技术文章早于博客推出
前言
在理解 MyBatis 架构以及核心内容剖析后,咱们能够钻研 MyBatis 执行过程,包含
- MyBatis 初始化
- SQL 执行过程
而且在面试会问到一下对于 MyBatis 初始化的问题,比方:
- Mybatis 须要初始化哪些?
- MyBatis 初始化的过程?
MyBatis 初始化
在 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)
办法,看看具体流程图:
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {ErrorContext.instance().reset();
try {inputStream.close();
} catch (IOException e) {// Intentionally ignore. Prefer previous error.}
}
}
首先会应用 XMLConfigBuilder::parser()
解析mybatis-config.xml
配置文件,
- 先解析标签
configuration
内的数据封装成XNode
,configuration
也是 MyBatis 中最重要的一个标签 - 依据
XNode
解析mybatis-config.xml
配置文件的各个标签转变为各个对象
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(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
,看看加载流程图来剖析剖析
通过 XPathParser::evalNode
将mapper
标签中内容解析到XNode
public void parse() {if (!this.configuration.isResourceLoaded(this.resource)) {this.configurationElement(this.parser.evalNode("/mapper"));
this.configuration.addLoadedResource(this.resource);
this.bindMapperForNamespace();}
this.parsePendingResultMaps();
this.parsePendingCacheRefs();
this.parsePendingStatements();}
再由 configurationElement()
办法去解析 XNode
中的各个标签:
namespace
parameterMap
resultMap
select|insert|update|delete
private void configurationElement(XNode context) {
try {String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {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"));
// 解析 MapperState
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()
,遍历 <select />
、<insert />
、<update />
、<delete />
节点们,一一创立 XMLStatementBuilder
对象,执行解析, 通过 XMLStatementBuilder::parseStatementNode()
解析,
parameterType
resultType
selectKey
等
并会通过 LanguageDriver::createSqlSource()
(默认XmlLanguageDriver
) 解析动静 sql 生成 SqlSource
( 具体内容请看下个大节),
- 应用
GenericTokenParser::parser()
负责将 SQL 语句中的#{}
替换成相应的?
占位符,并获取该?
占位符对应的
而且通过 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;}
// 解析取得各种属性
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String resultMap = context.getStringAttribute("resultMap");
String resultType = context.getStringAttribute("resultType");
String lang = context.getStringAttribute("lang");
// 取得 lang 对应的 LanguageDriver 对象
LanguageDriver langDriver = getLanguageDriver(lang);
// 取得 resultType 对应的类
Class<?> resultTypeClass = resolveClass(resultType);
String resultSetType = context.getStringAttribute("resultSetType");
// 取得 statementType 对应的枚举值
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
// 取得 resultSet 对应的枚举值
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
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());
// 解析 <selectKey /> 标签
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// 创立 SqlSource 生成动静 sql
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
String resultSets = context.getStringAttribute("resultSets");
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
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;
}
// 创立 MappedStatement 对象
this.builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass,
resultMap, resultTypeClass, resultSetTypeEnum, flushCache,
useCache, resultOrdered, (KeyGenerator)keyGenerator, keyProperty,
keyColumn, databaseId, langDriver, resultSets);
}
解析 Mapper
接口中的注解
当执行完 XMLMapperBuilder::configurationElement()
办法后, 会调用 XMLMapperBuilder::bindMapperForNamespace()
会转换成对接口上注解进行扫描,具体通过 MapperRegistry::addMapper()
调用 MapperAnnotationBuilder
实现的
MapperAnnotationBuilder::parse()
是注解结构器,负责解析 Mapper
接口上的注解, 解析时须要留神防止和 XMLMapperBuilder::parse()
办法抵触,反复解析,最终应用 parseStatement
解析,那怎么操作?
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();
// 遍历每个办法,解析其上的注解
Method[] methods = type.getMethods();
for (Method method : methods) {
try {if (!method.isBridge()) {
// 执行解析
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) {
// 获取接口参数类型
Class<?> parameterTypeClass = getParameterType(method);
// 加载语言处理器,默认 XmlLanguageDriver
LanguageDriver languageDriver = getLanguageDriver(method);
// 依据 LanguageDriver,GenericTokenParser 生成动静 SQL
SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
if (sqlSource != null) {
// 获取其余属性
Options options = method.getAnnotation(Options.class);
final String mappedStatementId = type.getName() + "." + method.getName();
Integer fetchSize = null;
Integer timeout = null;
StatementType statementType = StatementType.PREPARED;
ResultSetType resultSetType = null;
SqlCommandType sqlCommandType = getSqlCommandType(method);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = !isSelect;
boolean useCache = isSelect;
// 取得 KeyGenerator 对象
KeyGenerator keyGenerator;
String keyProperty = null;
String keyColumn = null;
if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) { // 有
// first check for SelectKey annotation - that overrides everything else
// 如果有 @SelectKey 注解,则进行解决
SelectKey selectKey = method.getAnnotation(SelectKey.class);
if (selectKey != null) {keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
keyProperty = selectKey.keyProperty();
// 如果无 @Options 注解,则依据全局配置解决
} else if (options == null) {keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
// 如果有 @Options 注解,则应用该注解的配置解决
} else {keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
keyProperty = options.keyProperty();
keyColumn = options.keyColumn();}
// 无
} else {keyGenerator = NoKeyGenerator.INSTANCE;}
// 初始化各种属性
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();
resultSetType = options.resultSetType();}
// 取得 resultMapId 编号字符串
String resultMapId = null;
// 如果有 @ResultMap 注解,应用该注解为 resultMapId 属性
ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
if (resultMapAnnotation != null) {String[] resultMaps = resultMapAnnotation.value();
StringBuilder sb = new StringBuilder();
for (String resultMap : resultMaps) {if (sb.length() > 0) {sb.append(",");
}
sb.append(resultMap);
}
resultMapId = sb.toString();
// 如果无 @ResultMap 注解,解析其它注解,作为 resultMapId 属性
} else if (isSelect) {resultMapId = parseResultMap(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,
// DatabaseID
null,
languageDriver,
// ResultSets
options != null ? nullOrEmpty(options.resultSets()) : null);
}
}
生成动静SqlSource
当在执行 langDriver::createSqlSource(configuration, context, parameterTypeClass)
中的时候,是怎么从 Mapper XML
或办法注解上读取 SQL
内容生成动静 SqlSource
的呢?当初来一探到底,
首先须要获取 langDriver
实现 XMLLanguageDriver
/RawLanguageDriver
,当初应用默认的XMLLanguageDriver::createSqlSource(configuration, context, parameterTypeClass)
开启创立,再应用 XMLScriptBuilder::parseScriptNode()
解析生成SqlSource
DynamicSqlSource
:动静的SqlSource
实现类 , 实用于应用了 OGNL 表达式,或者应用了${}
表达式的 SQLRawSqlSource
:原始 的SqlSource
实现类 , 实用于仅应用#{}
表达式,或者不应用任何表达式的状况
public SqlSource parseScriptNode() {MixedSqlNode rootSqlNode = this.parseDynamicTags(this.context);
Object sqlSource;
if (this.isDynamic) {sqlSource = new DynamicSqlSource(this.configuration, rootSqlNode);
} else {sqlSource = new RawSqlSource(this.configuration, rootSqlNode, this.parameterType);
}
return (SqlSource)sqlSource;
}
那就抉择其中一种来剖析一下 RawSqlSource
,怎么实现结构的呢?看看RawSqlSource
构造函数:
public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> clazz = parameterType == null ? Object.class : parameterType;
this.sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap());
}
应用 SqlSourceBuilder::parse()
去解析 SQl,外面又什么神奇的中央呢?
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {SqlSourceBuilder.ParameterMappingTokenHandler handler = new SqlSourceBuilder.ParameterMappingTokenHandler(this.configuration, parameterType, additionalParameters);
// 创立基于 #{}的 GenericTokenParser
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
String sql = parser.parse(originalSql);
return new StaticSqlSource(this.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
供应用户进行操作。
各位看官还能够吗?喜爱的话,动动手指导个赞???? 呗!!谢谢反对!
欢送扫码关注,原创技术文章第一工夫推出