Mybatis源碼閱讀(一):Mybatis初始化1.3 —— 解析sql片段和sql節點

*************************************優雅的分割線 **********************************

分享一波:程序員賺外快-必看的巔峰干貨

如果以上內容對你覺得有用,并想獲取更多的賺錢方式和免費的技術教程

請關注微信公眾號:HB荷包
在這里插入圖片描述
一個能讓你學習技術和賺錢方法的公眾號,持續更新

前言

接上一篇博客,解析核心配置文件的流程還剩兩塊。Mybatis初始化1.2 —— 解析別名、插件、對象工廠、反射工具箱、環境

本想著只是兩個模塊,隨便寫寫就完事,沒想到內容還不少,加上最近幾天事情比較多,就沒怎么更新,幾天抽空編寫剩下兩塊代碼。
解析sql片段

sql節點配置在Mapper.xml文件中,用于配置一些常用的sql片段。

/*** 解析sql節點。* sql節點用于定義一些常用的sql片段* @param list*/
private void sqlElement(List<XNode> list) {if (configuration.getDatabaseId() != null) {sqlElement(list, configuration.getDatabaseId());}sqlElement(list, null);
}/*** 解析sql節點* @param list sql節點集合* @param requiredDatabaseId 當前配置的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");// 啟用當前的命名空間id = builderAssistant.applyCurrentNamespace(id, false);if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {// 如果該節點指定的databaseId是當前配置中的,就啟用該節點的sql片段sqlFragments.put(id, context);}}
}

這里面,SQLFragments用于存放sql片段。在存放sql片段之前,會先調用databaseIdMatchesCurrent方法去校驗該片段的databaseId是否為當前啟用的databaseId

/*** 判斷databaseId是否是當前啟用的* @param id 命名空間id* @param databaseId 待匹配的databaseId* @param requiredDatabaseId 當前啟用的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節點的代碼。
解析sql節點

在XxxMapper.xml中存在諸多的sql節點,大體分為select、insert、delete、update節點(此外還有selectKey節點等,后面會進行介紹)。每一個sql節點最終會被解析成MappedStatement。

/**

  • 表示映射文件中的sql節點

  • select、update、insert、delete節點

  • 該節點中包含了id、返回值、sql等屬性

  • @author Clinton Begin
    */
    public final class MappedStatement {

    /**

    • 包含命名空間的節點id
      /
      private String resource;
      private Configuration configuration;
      /
      *
    • 節點id
      /
      private String id;
      private Integer fetchSize;
      private Integer timeout;
      /
      *
    • STATEMENT 表示簡單的sql,不包含動態的
    • PREPARED 表示預編譯sql,包含#{}
    • CALLABLE 調用存儲過程
      */
      private StatementType statementType;
      private ResultSetType resultSetType;

    /**

    • 節點或者注解中編寫的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;
      }

處理sql節點

/*** 處理sql節點* 這里的Statement單詞后面會經常遇到* 一個MappedStatement表示一條sql語句* @param list*/
private void buildStatementFromContext(List<XNode> list) {if (configuration.getDatabaseId() != null) {buildStatementFromContext(list, configuration.getDatabaseId());}buildStatementFromContext(list, null);
}/*** 啟用當前databaseId的sql語句節點* @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節點statementParser.parseStatementNode();} catch (IncompleteElementException e) {configuration.addIncompleteStatement(statementParser);}}
}

在parseStatementNode方法中,只會啟用當前databaseId的sql節點(如果沒配置就全部啟用)

/*** 解析sql節點*/
public void parseStatementNode() {// 當前節點idString id = context.getStringAttribute("id");// 獲取數據庫idString databaseId = context.getStringAttribute("databaseId");// 啟用的數據庫和sql節點配置的不同if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {return;}// 獲取當前節點的名稱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節點......

}

在該方法中,會依次處理include節點、selectKey節點、最后獲取到當前sql節點的各個屬性,去創建MappedStatement對象,并添加到Configuration中。

/*** 解析sql節點*/
public void parseStatementNode() {// 在上面已經進行了注釋......// 解析sql前先處理include節點。XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);includeParser.applyIncludes(context.getNode());// 獲取parameterType屬性String parameterType = context.getStringAttribute("parameterType");// 直接拿到parameterType對應的ClassClass<?> parameterTypeClass = resolveClass(parameterType);// 獲取到lang屬性String lang = context.getStringAttribute("lang");// 獲取對應的動態sql語言驅動器。LanguageDriver langDriver = getLanguageDriver(lang);// 解析selectKey節點processSelectKeyNodes(id, parameterTypeClass, langDriver);

}

解析parameterType和lang屬性比較簡單,這里只看解析include和selectKey
解析include節點

/*** 啟用include節點** @param source*/
public void applyIncludes(Node source) {Properties variablesContext = new Properties();Properties configurationVariables = configuration.getVariables();Optional.ofNullable(configurationVariables).ifPresent(variablesContext::putAll);applyIncludes(source, variablesContext, false);
}

在applyIncludes方法中,會調用它的重載方法,遞歸去處理所有的include節點。include節點中,可能會存在${}占位符,在這步,也會將該占位符給替換成實際意義的字符串。接著,include節點會被處理成sql節點,并將sql節點中的sql語句取出放到節點之前,最后刪除sql節點。最終select等節點會被解析成帶有動態sql的節點。

/*** 遞歸去處理所有的include節點.** @param source           include節點* @param variablesContext 當前所有的配置*/
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節點下的Properties節點,并替換value對應的占位符,將name和value鍵值對形式存放到variableContextProperties toIncludeContext = getVariablesContext(source, variablesContext);// 遞歸處理,在sql節點中可能會使用到include節點applyIncludes(toInclude, toIncludeContext, true);if (toInclude.getOwnerDocument() != source.getOwnerDocument()) {toInclude = source.getOwnerDocument().importNode(toInclude, true);}// 將include節點替換成sql節點source.getParentNode().replaceChild(toInclude, source);while (toInclude.hasChildNodes()) {// 如果還有子節點,就添加到sql節點前面// 在上面的代碼中,sql節點已經不可能再有子節點了// 這里的子節點就是文本節點(具體的sql語句)toInclude.getParentNode().insertBefore(toInclude.getFirstChild(), toInclude);}// 刪除sql節點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));}}// 獲取所有的子節點NodeList children = source.getChildNodes();for (int i = 0; i < children.getLength(); i++) {// 解析include節點applyIncludes(children.item(i), variablesContext, included);}} else if (included && (source.getNodeType() == Node.TEXT_NODE || source.getNodeType() == Node.CDATA_SECTION_NODE)&& !variablesContext.isEmpty()) {// 使用之前解析到的Properties對象替換對應的占位符source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext));}
}

第一行代碼的含義是根據include節點的refid屬性去獲取到對應的sql片段,代碼比較簡單

/*** 根據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中查找對應的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節點就會被替換成有實際意義的sql語句。
解析selectKey節點

當數據表中主鍵設計為自增,可能會存在業務需要在插入后獲取到主鍵,這時候就需要使用selectKey節點。processSelectKeyNodes方法用于解析selectKey節點。該方法會先獲取到該sql節點所有的selectKey節點,遍歷去解析,解析完畢后刪除selectKey節點。

/*** 解析selectKey節點* selectKey節點可以解決insert時主鍵自增問題* 如果需要在插入數據后獲取到主鍵,就需要使用selectKey節點** @param id                 sql節點的id* @param parameterTypeClass 參數類型* @param langDriver         動態sql語言驅動器*/
private void processSelectKeyNodes(String id, Class<?> parameterTypeClass, LanguageDriver langDriver) {// 獲取全部的selectKey節點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節點的代碼比較簡單,這里就不貼了,重點看parseSelectKeyNodes方法。

該方法負責遍歷獲取到的所有selectKey節點,只啟用當前databaseId對應的節點(這里的邏輯和sql片段那里一樣,如果開發者沒有配置databaseId,就全部啟用)

/*** 解析selectKey節點** @param parentId             父節點id(指sql節點的id)* @param list                 所有的selectKey節點* @param parameterTypeClass   參數類型* @param langDriver           動態sql驅動* @param skRequiredDatabaseId 數據源id*/
private void parseSelectKeyNodes(String parentId, List<XNode> list, Class<?> parameterTypeClass, LanguageDriver langDriver, String skRequiredDatabaseId) {// 遍歷selectKey節點for (XNode nodeToHandle : list) {// 拼接id 修改為形如 findById!selectKey形式String id = parentId + SelectKeyGenerator.SELECT_KEY_SUFFIX;// 獲得當前節點的databaseId屬性String databaseId = nodeToHandle.getStringAttribute("databaseId");// 只解析databaseId是當前啟用databaseId的節點if (databaseIdMatchesCurrent(id, databaseId, skRequiredDatabaseId)) {parseSelectKeyNode(id, nodeToHandle, parameterTypeClass, langDriver, databaseId);}}
}

在for循環中,會逐個調用parseSelectKeyNode方法去解析selectKey節點。代碼看似復雜其實很簡單,最終selectKey節點也會被解析成MappedStatement對象

/*** 解析selectKey節點** @param id                 節點id* @param nodeToHandle       selectKey節點* @param parameterTypeClass 參數類型* @param langDriver         動態sql驅動* @param databaseId         數據庫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、動態sql、存儲過程)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"));// 設置MappedStatement對象需要的一系列屬性默認值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節點只能配置select語句SqlCommandType sqlCommandType = SqlCommandType.SELECT;// 用這么一大坨東西去創建MappedStatement對象并添加到Configuration中builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,resultSetTypeEnum, flushCache, useCache, resultOrdered,keyGenerator, keyProperty, keyColumn, databaseId, langDriver, null);// 啟用當前命名空間(給id前面加上命名空間)id = builderAssistant.applyCurrentNamespace(id, false);// 從Configuration中拿到上面的MappedStatementMappedStatement keyStatement = configuration.getMappedStatement(id, false);configuration.addKeyGenerator(id, new SelectKeyGenerator(keyStatement, executeBefore));
}

至此,selectKey節點已經被解析完畢并刪除掉了,其余代碼就是負責解析其他屬性并將該sql節點創建為MappedStatement對象。

    KeyGenerator keyGenerator;// 拼接id。形如findById!selectKeyString keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;// 給這個id前面追加當前的命名空間keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);if (configuration.hasKeyGenerator(keyStatementId)) {keyGenerator = configuration.getKeyGenerator(keyStatementId);} else {// 優先取配置的useGeneratorKeys。如果為空就判斷當前配置是否允許jdbc自動生成主鍵,并且當前是insert語句// 判斷如果為真就創建Jdbc3KeyGenerator,如果為假就創建NoKeyGeneratorkeyGenerator = context.getBooleanAttribute("useGeneratedKeys",configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;}// 獲取當前sql節點的一堆屬性,去創建MappedStatement。// 這里創建的MappedStatement就代表一個sql節點// 也是后面編寫mybatis攔截器時可以攔截的一處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);

結語

在看本博客時,可能會覺得比較吃力,這里建議結合代碼去閱讀。事實上這三篇博客的閱讀和編寫的過程中,對應的mybatis代碼都比較容易,結合代碼閱讀起來并沒有多大難度。最后貼一下我的碼云地址(別問為什么是github,卡的一批)

mybatis源碼中文注釋

*************************************優雅的分割線 **********************************

分享一波:程序員賺外快-必看的巔峰干貨

如果以上內容對你覺得有用,并想獲取更多的賺錢方式和免費的技術教程

請關注微信公眾號:HB荷包
在這里插入圖片描述
一個能讓你學習技術和賺錢方法的公眾號,持續更新

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:
http://www.pswp.cn/news/536983.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/536983.shtml
英文地址,請注明出處:http://en.pswp.cn/news/536983.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

IBM研究院計畫5年改變人類生活創新預測

IBM研究院近日發布未來5年將會改變人類生活方式的5項創新預測&#xff08;IBM 5 in 5&#xff09;&#xff0c;包含透過數字分身&#xff08;Digital Twin&#xff09;農業將用更少的資源供給不斷增長的人口、區塊鏈能防范更多的食物浪費、用微生物基因組群保護人類受到有害細菌…

添加請求頭 retrofit_RxJava 與 Retrofit 結合的最佳實踐

前言RxJava和Retrofit也火了一段時間了&#xff0c;不過最近一直在學習ReactNative和Node相關的姿勢&#xff0c;一直沒有時間研究這些新東西&#xff0c;最近有個項目準備寫&#xff0c;打算先用Android寫一個Demo出來&#xff0c;卻發現Android的世界發生了天翻地覆的變化&am…

Mybatis源碼閱讀(二):動態節點解析2.1 —— SqlSource和SqlNode

*************************************優雅的分割線 ********************************** 分享一波:程序員賺外快-必看的巔峰干貨 如果以上內容對你覺得有用,并想獲取更多的賺錢方式和免費的技術教程 請關注微信公眾號:HB荷包 一個能讓你學習技術和賺錢方法的公眾號,持續更…

k8s邊緣節點_邊緣計算,如何啃下集群管理這塊硬骨頭?

導讀邊緣計算平臺&#xff0c;旨在將邊緣端靠近數據源的計算單元納入到中心云&#xff0c;實現集中管理&#xff0c;將云服務部署其上&#xff0c;及時響應終端請求。然而&#xff0c;成千上萬的邊緣節點散布于各地&#xff0c;例如銀行網點、車載節點等&#xff0c;節點數量甚…

Mybatis源碼閱讀(二):動態節點解析2.2 —— SqlSourceBuilder與三種SqlSource

*************************************優雅的分割線 ********************************** 分享一波:程序員賺外快-必看的巔峰干貨 如果以上內容對你覺得有用,并想獲取更多的賺錢方式和免費的技術教程 請關注微信公眾號:HB荷包 一個能讓你學習技術和賺錢方法的公眾號,持續更…

搞懂toString()與valueOf()的區別

一、toString&#xff08;&#xff09; 作用&#xff1a;toString&#xff08;&#xff09;方法返回一個表示改對象的字符串&#xff0c;如果是對象會返回&#xff0c;toString() 返回 “[object type]”,其中type是對象類型。 二、valueOf( ) 作用&#xff1a;valueOf房啊發返…

oracle入庫的速度能到多少_倒車入庫別練復雜了,其實就這兩點

教練總會讓學員反復練倒車入庫&#xff0c;但不少學員都會有這樣的疑惑&#xff1a;為什么每一次倒庫結果都不一樣&#xff0c;倒車入庫的練習重點是什么&#xff1f;倒車入庫是科二的重點及難點&#xff0c;但只要掌握以下兩個關鍵&#xff0c;順利通過真不難&#xff1a;01方…

Mybatis源碼閱讀(三):結果集映射3.1 —— ResultSetBuilder與簡單映射

*************************************優雅的分割線 ********************************** 分享一波:程序員賺外快-必看的巔峰干貨 如果以上內容對你覺得有用,并想獲取更多的賺錢方式和免費的技術教程 請關注微信公眾號:HB荷包 一個能讓你學習技術和賺錢方法的公眾號,持續更…

kdj買賣指標公式源碼_通達信指標公式源碼MACD背離KDJ背離指標

N1:5;N2:10;N3:21;N4:60;牛熊:EMA(CLOSE,N4),COLORGREEN,LINETHICK3;DIFF:EMA(CLOSE,12) - EMA(CLOSE,26);DEA:EMA(DIFF,8);A1:BARSLAST(REF(CROSS(DIFF,DEA),1)); B1:REF(C,A11)>C AND REF(DIFF,A11)DRAWTEXT(IF(B1>0,1,0),L-0.1,MACD底背),COLORGREEN;RSV:(CLOSE-LLV(L…

Mybatis源碼閱讀(三):結果集映射3.2 —— 嵌套映射

*************************************優雅的分割線 ********************************** 分享一波:程序員賺外快-必看的巔峰干貨 如果以上內容對你覺得有用,并想獲取更多的賺錢方式和免費的技術教程 請關注微信公眾號:HB荷包 一個能讓你學習技術和賺錢方法的公眾號,持續更…

18.requests

多多的的轉載于:https://www.cnblogs.com/yangyangchunchun/p/10368337.html

gridview獲取選中行數據_Word轉Excel,不想熬夜加班,那就掌握這個數據清洗方法...

私信回復關鍵詞【福利】~獲取豐富辦公資源&#xff0c;助你高效辦公早下班&#xff01;小伙伴們&#xff0c;大家好&#xff0c;我是專治各種疑難雜「數」的農夫~今天&#xff0c;我就為大家介紹一種高效的數據清洗方法&#xff0c;助你告別熬夜加班&#xff0c;擁抱美好的夜晚…

如何深入學習python_菜鳥如何學好python

python在我國發展得如火如荼&#xff0c;因其操作簡單&#xff0c;應用廣泛受到很多人的喜歡。下面小編就來說說菜鳥如何學好python&#xff0c;一起來看看吧!1. 了解編程的基礎知識種是變量、編程規范、基本語法等&#xff0c;這也是開始編寫Python代碼的先決條件。第二種是數…

HTML5中本地儲存概念是什么,什么優點 ,與cookie有什么區別?

html5中的Web Storage 包括了兩種存儲方式&#xff1a; sessionStorage 和 localStorage. seessionStorage 用于本地存儲一個會話&#xff08;session&#xff09;中的數據&#xff0c;這些數據只有在同一個會話中的頁面才能訪問并且當會話結束后數據也隨之銷毀。因此session…

Mybatis源碼閱讀(三):結果集映射3.3 —— 主鍵生成策略

*************************************優雅的分割線 ********************************** 分享一波:程序員賺外快-必看的巔峰干貨 如果以上內容對你覺得有用,并想獲取更多的賺錢方式和免費的技術教程 請關注微信公眾號:HB荷包 一個能讓你學習技術和賺錢方法的公眾號,持續更…

list最大容量_Java 基礎(四)集合源碼解析 List

List 接口前面我們學習了Iterator、Collection&#xff0c;為集合的學習打下了基礎&#xff0c;現在我們來學習集合的第一大體系 List。List 是一個接口&#xff0c;定義了一組元素是有序的、可重復的集合。List 繼承自 Collection&#xff0c;較之 Collection&#xff0c;List…

Mybatis源碼閱讀(四):核心接口4.1——StatementHandler

*************************************優雅的分割線 ********************************** 分享一波:程序員賺外快-必看的巔峰干貨 如果以上內容對你覺得有用,并想獲取更多的賺錢方式和免費的技術教程 請關注微信公眾號:HB荷包 一個能讓你學習技術和賺錢方法的公眾號,持續更…

Shell學習之結合正則表達式與通配符的使用(五)

Shell學習之結合正則表達式與通配符的使用 目錄 通配符 正則表達式與通配符通配符通配符的使用正則表達式 正則表達式正則表達式的使用通配符 正則表達式與通配符 正則表達式用來在文件中匹配符合條件的字符串&#xff0c;正則是包含匹配。grep、awk、sed等命令可以支持正則表達…

Mybatis源碼閱讀(四):核心接口4.2——Executor(上)

*************************************優雅的分割線 ********************************** 分享一波:程序員賺外快-必看的巔峰干貨 如果以上內容對你覺得有用,并想獲取更多的賺錢方式和免費的技術教程 請關注微信公眾號:HB荷包 一個能讓你學習技術和賺錢方法的公眾號,持續更…

接收xml參數_SpringBoot實戰(二):接收xml請求

強烈推薦一個大神的人工智能的教程&#xff1a;http://www.captainbed.net/zhanghan【前言】最近在對接一個第三方系統&#xff0c;需要接收第三方系統的回調&#xff0c;而且格式為XML形式&#xff0c;之前自己一般接收的參數是Json形式&#xff0c;于是乎做個實驗驗證一下使用…