Mybatis源码阅读(一):Mybatis初始化1.3 —— 解析sql片段和sql节点
*************************************優(yōu)雅的分割線 **********************************
分享一波:程序員賺外快-必看的巔峰干貨
如果以上內(nèi)容對你覺得有用,并想獲取更多的賺錢方式和免費(fèi)的技術(shù)教程
請關(guān)注微信公眾號:HB荷包
一個(gè)能讓你學(xué)習(xí)技術(shù)和賺錢方法的公眾號,持續(xù)更新
前言
接上一篇博客,解析核心配置文件的流程還剩兩塊。Mybatis初始化1.2 —— 解析別名、插件、對象工廠、反射工具箱、環(huán)境
本想著只是兩個(gè)模塊,隨便寫寫就完事,沒想到內(nèi)容還不少,加上最近幾天事情比較多,就沒怎么更新,幾天抽空編寫剩下兩塊代碼。
解析sql片段
sql節(jié)點(diǎn)配置在Mapper.xml文件中,用于配置一些常用的sql片段。
/*** 解析sql節(jié)點(diǎn)。* sql節(jié)點(diǎn)用于定義一些常用的sql片段* @param list*/ private void sqlElement(List<XNode> list) {if (configuration.getDatabaseId() != null) {sqlElement(list, configuration.getDatabaseId());}sqlElement(list, null); }/*** 解析sql節(jié)點(diǎn)* @param list sql節(jié)點(diǎn)集合* @param requiredDatabaseId 當(dāng)前配置的databaseId*/ private void sqlElement(List<XNode> list, String requiredDatabaseId) {for (XNode context : list) {// 獲取databaseId和id屬性String databaseId = context.getStringAttribute("databaseId");// 這里的id指定的是命名空間String id = context.getStringAttribute("id");// 啟用當(dāng)前的命名空間id = builderAssistant.applyCurrentNamespace(id, false);if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {// 如果該節(jié)點(diǎn)指定的databaseId是當(dāng)前配置中的,就啟用該節(jié)點(diǎn)的sql片段sqlFragments.put(id, context);}} }這里面,SQLFragments用于存放sql片段。在存放sql片段之前,會(huì)先調(diào)用databaseIdMatchesCurrent方法去校驗(yàn)該片段的databaseId是否為當(dāng)前啟用的databaseId
/*** 判斷databaseId是否是當(dāng)前啟用的* @param id 命名空間id* @param databaseId 待匹配的databaseId* @param requiredDatabaseId 當(dāng)前啟用的databaseId* @return*/ private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {if (requiredDatabaseId != null) {return requiredDatabaseId.equals(databaseId);}if (databaseId != null) {return false;}if (!this.sqlFragments.containsKey(id)) {return true;}// skip this fragment if there is a previous one with a not null databaseIdXNode context = this.sqlFragments.get(id);return context.getStringAttribute("databaseId") == null; }解析sql片段的步驟就這么簡單,下面是解析sql節(jié)點(diǎn)的代碼。
解析sql節(jié)點(diǎn)
在XxxMapper.xml中存在諸多的sql節(jié)點(diǎn),大體分為select、insert、delete、update節(jié)點(diǎn)(此外還有selectKey節(jié)點(diǎn)等,后面會(huì)進(jìn)行介紹)。每一個(gè)sql節(jié)點(diǎn)最終會(huì)被解析成MappedStatement。
/**
-
表示映射文件中的sql節(jié)點(diǎn)
-
select、update、insert、delete節(jié)點(diǎn)
-
該節(jié)點(diǎn)中包含了id、返回值、sql等屬性
-
@author Clinton Begin
*/
public final class MappedStatement {/**
- 包含命名空間的節(jié)點(diǎn)id
/
private String resource;
private Configuration configuration;
/* - 節(jié)點(diǎn)id
/
private String id;
private Integer fetchSize;
private Integer timeout;
/* - STATEMENT 表示簡單的sql,不包含動(dòng)態(tài)的
- PREPARED 表示預(yù)編譯sql,包含#{}
- CALLABLE 調(diào)用存儲(chǔ)過程
*/
private StatementType statementType;
private ResultSetType resultSetType;
/**
- 節(jié)點(diǎn)或者注解中編寫的sql
/
private SqlSource sqlSource;
private Cache cache;
private ParameterMap parameterMap;
private List resultMaps;
private boolean flushCacheRequired;
private boolean useCache;
private boolean resultOrdered;
/* - sql的類型。select、update、insert、delete
*/
private SqlCommandType sqlCommandType;
private KeyGenerator keyGenerator;
private String[] keyProperties;
private String[] keyColumns;
private boolean hasNestedResultMaps;
private String databaseId;
private Log statementLog;
private LanguageDriver lang;
private String[] resultSets;
}
- 包含命名空間的節(jié)點(diǎn)id
處理sql節(jié)點(diǎn)
/*** 處理sql節(jié)點(diǎn)* 這里的Statement單詞后面會(huì)經(jīng)常遇到* 一個(gè)MappedStatement表示一條sql語句* @param list*/ private void buildStatementFromContext(List<XNode> list) {if (configuration.getDatabaseId() != null) {buildStatementFromContext(list, configuration.getDatabaseId());}buildStatementFromContext(list, null); }/*** 啟用當(dāng)前databaseId的sql語句節(jié)點(diǎn)* @param list* @param requiredDatabaseId*/ private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {for (XNode context : list) {final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);try {// 解析sql節(jié)點(diǎn)statementParser.parseStatementNode();} catch (IncompleteElementException e) {configuration.addIncompleteStatement(statementParser);}} }在parseStatementNode方法中,只會(huì)啟用當(dāng)前databaseId的sql節(jié)點(diǎn)(如果沒配置就全部啟用)
/*** 解析sql節(jié)點(diǎn)*/ public void parseStatementNode() {// 當(dāng)前節(jié)點(diǎn)idString id = context.getStringAttribute("id");// 獲取數(shù)據(jù)庫idString databaseId = context.getStringAttribute("databaseId");// 啟用的數(shù)據(jù)庫和sql節(jié)點(diǎn)配置的不同if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {return;}// 獲取當(dāng)前節(jié)點(diǎn)的名稱String nodeName = context.getNode().getNodeName();// 獲取到sql的類型。select|update|delete|insertSqlCommandType 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);// 下面是解析include和selectKey節(jié)點(diǎn)......}
在該方法中,會(huì)依次處理include節(jié)點(diǎn)、selectKey節(jié)點(diǎn)、最后獲取到當(dāng)前sql節(jié)點(diǎn)的各個(gè)屬性,去創(chuàng)建MappedStatement對象,并添加到Configuration中。
/*** 解析sql節(jié)點(diǎn)*/ public void parseStatementNode() {// 在上面已經(jīng)進(jìn)行了注釋......// 解析sql前先處理include節(jié)點(diǎn)。XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);includeParser.applyIncludes(context.getNode());// 獲取parameterType屬性String parameterType = context.getStringAttribute("parameterType");// 直接拿到parameterType對應(yīng)的ClassClass<?> parameterTypeClass = resolveClass(parameterType);// 獲取到lang屬性String lang = context.getStringAttribute("lang");// 獲取對應(yīng)的動(dòng)態(tài)sql語言驅(qū)動(dòng)器。LanguageDriver langDriver = getLanguageDriver(lang);// 解析selectKey節(jié)點(diǎn)processSelectKeyNodes(id, parameterTypeClass, langDriver);}
解析parameterType和lang屬性比較簡單,這里只看解析include和selectKey
解析include節(jié)點(diǎn)
在applyIncludes方法中,會(huì)調(diào)用它的重載方法,遞歸去處理所有的include節(jié)點(diǎn)。include節(jié)點(diǎn)中,可能會(huì)存在${}占位符,在這步,也會(huì)將該占位符給替換成實(shí)際意義的字符串。接著,include節(jié)點(diǎn)會(huì)被處理成sql節(jié)點(diǎn),并將sql節(jié)點(diǎn)中的sql語句取出放到節(jié)點(diǎn)之前,最后刪除sql節(jié)點(diǎn)。最終select等節(jié)點(diǎn)會(huì)被解析成帶有動(dòng)態(tài)sql的節(jié)點(diǎn)。
/*** 遞歸去處理所有的include節(jié)點(diǎn).** @param source include節(jié)點(diǎn)* @param variablesContext 當(dāng)前所有的配置*/ private void applyIncludes(Node source, final Properties variablesContext, boolean included) {if (source.getNodeName().equals("include")) {// 獲取到refid并從配置中拿到sql片段Node toInclude = findSqlFragment(getStringAttribute(source, "refid"), variablesContext);// 解析include節(jié)點(diǎn)下的Properties節(jié)點(diǎn),并替換value對應(yīng)的占位符,將name和value鍵值對形式存放到variableContextProperties toIncludeContext = getVariablesContext(source, variablesContext);// 遞歸處理,在sql節(jié)點(diǎn)中可能會(huì)使用到include節(jié)點(diǎn)applyIncludes(toInclude, toIncludeContext, true);if (toInclude.getOwnerDocument() != source.getOwnerDocument()) {toInclude = source.getOwnerDocument().importNode(toInclude, true);}// 將include節(jié)點(diǎn)替換成sql節(jié)點(diǎn)source.getParentNode().replaceChild(toInclude, source);while (toInclude.hasChildNodes()) {// 如果還有子節(jié)點(diǎn),就添加到sql節(jié)點(diǎn)前面// 在上面的代碼中,sql節(jié)點(diǎn)已經(jīng)不可能再有子節(jié)點(diǎn)了// 這里的子節(jié)點(diǎn)就是文本節(jié)點(diǎn)(具體的sql語句)toInclude.getParentNode().insertBefore(toInclude.getFirstChild(), toInclude);}// 刪除sql節(jié)點(diǎn)toInclude.getParentNode().removeChild(toInclude);} else if (source.getNodeType() == Node.ELEMENT_NODE) {if (included && !variablesContext.isEmpty()) {NamedNodeMap attributes = source.getAttributes();for (int i = 0; i < attributes.getLength(); i++) {Node attr = attributes.item(i);attr.setNodeValue(PropertyParser.parse(attr.getNodeValue(), variablesContext));}}// 獲取所有的子節(jié)點(diǎn)NodeList children = source.getChildNodes();for (int i = 0; i < children.getLength(); i++) {// 解析include節(jié)點(diǎn)applyIncludes(children.item(i), variablesContext, included);}} else if (included && (source.getNodeType() == Node.TEXT_NODE || source.getNodeType() == Node.CDATA_SECTION_NODE)&& !variablesContext.isEmpty()) {// 使用之前解析到的Properties對象替換對應(yīng)的占位符source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext));} }第一行代碼的含義是根據(jù)include節(jié)點(diǎn)的refid屬性去獲取到對應(yīng)的sql片段,代碼比較簡單
/*** 根據(jù)refid查找sql片段* @param refid* @param variables* @return*/ private Node findSqlFragment(String refid, Properties variables) {// 替換占位符refid = PropertyParser.parse(refid, variables);// 將refid前面拼接命名空間refid = builderAssistant.applyCurrentNamespace(refid, true);try {// 從Configuration中查找對應(yīng)的sql片段XNode nodeToInclude = configuration.getSqlFragments().get(refid);return nodeToInclude.getNode().cloneNode(true);} catch (IllegalArgumentException e) {throw new IncompleteElementException("Could not find SQL statement to include with refid '" + refid + "'", e);} }到這里,include節(jié)點(diǎn)就會(huì)被替換成有實(shí)際意義的sql語句。
解析selectKey節(jié)點(diǎn)
當(dāng)數(shù)據(jù)表中主鍵設(shè)計(jì)為自增,可能會(huì)存在業(yè)務(wù)需要在插入后獲取到主鍵,這時(shí)候就需要使用selectKey節(jié)點(diǎn)。processSelectKeyNodes方法用于解析selectKey節(jié)點(diǎn)。該方法會(huì)先獲取到該sql節(jié)點(diǎn)所有的selectKey節(jié)點(diǎn),遍歷去解析,解析完畢后刪除selectKey節(jié)點(diǎn)。
/*** 解析selectKey節(jié)點(diǎn)* selectKey節(jié)點(diǎn)可以解決insert時(shí)主鍵自增問題* 如果需要在插入數(shù)據(jù)后獲取到主鍵,就需要使用selectKey節(jié)點(diǎn)** @param id sql節(jié)點(diǎn)的id* @param parameterTypeClass 參數(shù)類型* @param langDriver 動(dòng)態(tài)sql語言驅(qū)動(dòng)器*/ private void processSelectKeyNodes(String id, Class<?> parameterTypeClass, LanguageDriver langDriver) {// 獲取全部的selectKey節(jié)點(diǎn)List<XNode> selectKeyNodes = context.evalNodes("selectKey");if (configuration.getDatabaseId() != null) {parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, configuration.getDatabaseId());}parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, null);removeSelectKeyNodes(selectKeyNodes); }刪除selectKey節(jié)點(diǎn)的代碼比較簡單,這里就不貼了,重點(diǎn)看parseSelectKeyNodes方法。
該方法負(fù)責(zé)遍歷獲取到的所有selectKey節(jié)點(diǎn),只啟用當(dāng)前databaseId對應(yīng)的節(jié)點(diǎn)(這里的邏輯和sql片段那里一樣,如果開發(fā)者沒有配置databaseId,就全部啟用)
/*** 解析selectKey節(jié)點(diǎn)** @param parentId 父節(jié)點(diǎn)id(指sql節(jié)點(diǎn)的id)* @param list 所有的selectKey節(jié)點(diǎn)* @param parameterTypeClass 參數(shù)類型* @param langDriver 動(dòng)態(tài)sql驅(qū)動(dòng)* @param skRequiredDatabaseId 數(shù)據(jù)源id*/ private void parseSelectKeyNodes(String parentId, List<XNode> list, Class<?> parameterTypeClass, LanguageDriver langDriver, String skRequiredDatabaseId) {// 遍歷selectKey節(jié)點(diǎn)for (XNode nodeToHandle : list) {// 拼接id 修改為形如 findById!selectKey形式String id = parentId + SelectKeyGenerator.SELECT_KEY_SUFFIX;// 獲得當(dāng)前節(jié)點(diǎn)的databaseId屬性String databaseId = nodeToHandle.getStringAttribute("databaseId");// 只解析databaseId是當(dāng)前啟用databaseId的節(jié)點(diǎn)if (databaseIdMatchesCurrent(id, databaseId, skRequiredDatabaseId)) {parseSelectKeyNode(id, nodeToHandle, parameterTypeClass, langDriver, databaseId);}} }在for循環(huán)中,會(huì)逐個(gè)調(diào)用parseSelectKeyNode方法去解析selectKey節(jié)點(diǎn)。代碼看似復(fù)雜其實(shí)很簡單,最終selectKey節(jié)點(diǎn)也會(huì)被解析成MappedStatement對象
/*** 解析selectKey節(jié)點(diǎn)** @param id 節(jié)點(diǎn)id* @param nodeToHandle selectKey節(jié)點(diǎn)* @param parameterTypeClass 參數(shù)類型* @param langDriver 動(dòng)態(tài)sql驅(qū)動(dòng)* @param databaseId 數(shù)據(jù)庫id*/ private void parseSelectKeyNode(String id, XNode nodeToHandle, Class<?> parameterTypeClass, LanguageDriver langDriver, String databaseId) {// 獲取 resultType 屬性String resultType = nodeToHandle.getStringAttribute("resultType");// 解析返回值類型Class<?> resultTypeClass = resolveClass(resultType);// 解析statementType(sql類型,簡單sql、動(dòng)態(tài)sql、存儲(chǔ)過程)StatementType statementType = StatementType.valueOf(nodeToHandle.getStringAttribute("statementType", StatementType.PREPARED.toString()));// 獲取keyProperty和keyColumn屬性String keyProperty = nodeToHandle.getStringAttribute("keyProperty");String keyColumn = nodeToHandle.getStringAttribute("keyColumn");// 是在之前還是之后去獲取主鍵boolean executeBefore = "BEFORE".equals(nodeToHandle.getStringAttribute("order", "AFTER"));// 設(shè)置MappedStatement對象需要的一系列屬性默認(rèn)值boolean useCache = false;boolean resultOrdered = false;KeyGenerator keyGenerator = NoKeyGenerator.INSTANCE;Integer fetchSize = null;Integer timeout = null;boolean flushCache = false;String parameterMap = null;String resultMap = null;ResultSetType resultSetTypeEnum = null;// 生成sqlSourceSqlSource sqlSource = langDriver.createSqlSource(configuration, nodeToHandle, parameterTypeClass);// selectKey節(jié)點(diǎn)只能配置select語句SqlCommandType sqlCommandType = SqlCommandType.SELECT;// 用這么一大坨東西去創(chuàng)建MappedStatement對象并添加到Configuration中builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,resultSetTypeEnum, flushCache, useCache, resultOrdered,keyGenerator, keyProperty, keyColumn, databaseId, langDriver, null);// 啟用當(dāng)前命名空間(給id前面加上命名空間)id = builderAssistant.applyCurrentNamespace(id, false);// 從Configuration中拿到上面的MappedStatementMappedStatement keyStatement = configuration.getMappedStatement(id, false);configuration.addKeyGenerator(id, new SelectKeyGenerator(keyStatement, executeBefore)); }至此,selectKey節(jié)點(diǎn)已經(jīng)被解析完畢并刪除掉了,其余代碼就是負(fù)責(zé)解析其他屬性并將該sql節(jié)點(diǎn)創(chuàng)建為MappedStatement對象。
KeyGenerator keyGenerator;// 拼接id。形如findById!selectKeyString keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;// 給這個(gè)id前面追加當(dāng)前的命名空間keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);if (configuration.hasKeyGenerator(keyStatementId)) {keyGenerator = configuration.getKeyGenerator(keyStatementId);} else {// 優(yōu)先取配置的useGeneratorKeys。如果為空就判斷當(dāng)前配置是否允許jdbc自動(dòng)生成主鍵,并且當(dāng)前是insert語句// 判斷如果為真就創(chuàng)建Jdbc3KeyGenerator,如果為假就創(chuàng)建NoKeyGeneratorkeyGenerator = context.getBooleanAttribute("useGeneratedKeys",configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;}// 獲取當(dāng)前sql節(jié)點(diǎn)的一堆屬性,去創(chuàng)建MappedStatement。// 這里創(chuàng)建的MappedStatement就代表一個(gè)sql節(jié)點(diǎn)// 也是后面編寫mybatis攔截器時(shí)可以攔截的一處SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);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");Class<?> resultTypeClass = resolveClass(resultType);String resultMap = context.getStringAttribute("resultMap");String resultSetType = context.getStringAttribute("resultSetType");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");builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,resultSetTypeEnum, flushCache, useCache, resultOrdered,keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);結(jié)語
在看本博客時(shí),可能會(huì)覺得比較吃力,這里建議結(jié)合代碼去閱讀。事實(shí)上這三篇博客的閱讀和編寫的過程中,對應(yīng)的mybatis代碼都比較容易,結(jié)合代碼閱讀起來并沒有多大難度。最后貼一下我的碼云地址(別問為什么是github,卡的一批)
mybatis源碼中文注釋
*************************************優(yōu)雅的分割線 **********************************
分享一波:程序員賺外快-必看的巔峰干貨
如果以上內(nèi)容對你覺得有用,并想獲取更多的賺錢方式和免費(fèi)的技術(shù)教程
請關(guān)注微信公眾號:HB荷包
一個(gè)能讓你學(xué)習(xí)技術(shù)和賺錢方法的公眾號,持續(xù)更新
總結(jié)
以上是生活随笔為你收集整理的Mybatis源码阅读(一):Mybatis初始化1.3 —— 解析sql片段和sql节点的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux Mysql 安装方法
- 下一篇: ERROR 1045 (28000):