前言
在了解MyBatis架構以及核心內容分析后,我們可以研究MyBatis執行程序,包括
- MyBatis初始化
- SQL執行程序
而且在面試會問到一下關于MyBatis初始化的問題,比如:
- Mybatis需要初始化哪些?
- MyBatis初始化的程序?
MyBatis初始化
在 MyBatis 初始化程序中,會加載 mybatis-config.xml 組態檔、Mapper.xml映射組態檔以及 Mapper 介面中的注解資訊,決議后的配置資訊會形成相應的物件并保存到 Configuration 物件中,初始化程序可以分成三部分:
-
決議
mybatis-config.xml組態檔SqlSessionFactoryBuilderXMLConfigBuilderConfiguration
-
決議
Mapper.xml映射組態檔XMLMapperBuilder::parse()XMLStatementBuilder::parseStatementNode()XMLLanguageDriverSqlSourceMappedStatement
-
決議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中的各個標簽:
namespaceparameterMapresultMapselect|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()決議,
parameterTyperesultTypeselectKey等
并會通過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供給用戶進行操作,
各位看官還可以嗎?喜歡的話,動動手指點個贊💗唄!!謝謝支持!
歡迎掃碼關注,原創技術文章第一時間推出
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/122178.html
標籤:其他
上一篇:ipad揚聲器

