該系列文章收錄在公衆號【Ccww技術博客】,原創技術文章早於博客推出
在瞭解MyBatis架構以及核心內容分析後,咱們能夠研究MyBatis執行過程,包括java
並且在面試會問到一下關於MyBatis初始化的問題,好比:node
在 MyBatis 初始化過程當中,會加載 mybatis-config.xml
配置文件、Mapper.xml
映射配置文件以及 Mapper 接口中的註解信息,解析後的配置信息會造成相應的對象並保存到 Configuration
對象中。初始化過程能夠分紅三部分:面試
解析mybatis-config.xml
配置文件sql
SqlSessionFactoryBuilder
XMLConfigBuilder
Configuration
解析Mapper.xml
映射配置文件apache
XMLMapperBuilder::parse()
XMLStatementBuilder::parseStatementNode
()XMLLanguageDriver
SqlSource
MappedStatement
解析Mapper接口中的註解mybatis
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
配置文件,app
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
供給後續執行使用。ide
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
供給用戶進行操做。
各位看官還能夠嗎?喜歡的話,動動手指點個贊💗唄!!謝謝支持!
歡迎掃碼關注,原創技術文章第一時間推出
![]()