<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                ### 重新認識MappedStatement 每個MappedStatement對應了我們自定義Mapper接口中的一個方法,它保存了開發人員編寫的SQL語句、參數結構、返回值結構、Mybatis對它的處理方式的配置等細節要素,是對一個SQL命令是什么、執行方式的完整定義。可以說,有了它Mybatis就知道如何去調度四大組件順利的完成用戶請求。 MappedStatement保存在Configuration#mappedStatements這個Map類型的對象中,其存儲的key為MappedStatement#id,所以MappedStatement的id是不能重復的,這個id是由Mapper接口的完全限定名和方法名稱拼接而成,這就導致了我們在同一個Mapper中不能出現重載的接口方法。 按照Mybatis的規范,每個Mapper方法也會對應xml中一個select/insert/update/delete標簽,Mybatis為這些標簽設計了一些屬性,允許我們開發人員修改Mybatis的運行方式或行為。大部分情況下,我們不會關注這些屬性,是因為Mybatis為其設計了默認值,方便我們開箱即用。我把MappedStatement類中的主要字段進行了注釋,這些字段都可以找到與之對應的標簽屬性,可參考[《XML映射文件》](https://mybatis.org/mybatis-3/zh/sqlmap-xml.html),代碼貼在了下面,大家先對MappedStatement有個大概的認識。 ~~~java public final class MappedStatement { /** * 對應所屬mapper的資源路徑,如我們示例中的CompanyMapper.xml */ private String resource; /** * mybatis全局的配置對象 */ private Configuration configuration; /** * 當前MappedStatement的唯一識別ID,并且在同一個Configuration中是唯一的 * 它由Mapper類的完全限定名和Mapper方法名稱拼接而成 */ private String id; /** * mybatis每次從數據庫中返回記錄的大小,通過對該值的優化,可以提升查詢效率 */ private Integer fetchSize; /** * 當前MappedStatement執行時,數據庫操作的超時時間 */ private Integer timeout; /** * SQL聲明的類型,決定當前MappedStatement由哪種類型的StatementHandler執行 * StatementType枚舉有:STATEMENT, PREPARED, CALLABLE * 默認值是:PREPARED */ private StatementType statementType; /** * 結果集處理類型,決定了結果集游標的移動方式: * 只能向前移動、雙向移動且對修改敏感、雙向移動對修改不敏感。 */ private ResultSetType resultSetType; /** * 存儲我們定義的經過mybatis初步解析處理的sql語句,由若干sql節點構成,包含一些動態節點,如If條件語句。 * 在生成SqlSource之前,已經把<include></include>標簽的內容轉為了實際的文本對象 */ private SqlSource sqlSource; /** * 二級緩存策略配置對象 */ private Cache cache; /** * 參數映射,外部以何種形式對當前MappedStatement傳參 */ private ParameterMap parameterMap; /** * 結果映射列表,應該是只有一個的,不明白為啥是列表,可能是多個結果集返回時使用的。 */ private List<ResultMap> resultMaps; /** * 是否要刷新緩存,將其設置為 true 后,只要語句被調用,都會導致本地緩存和二級緩存被清空 * 對select命令,默認值為false,對insert、update、delete默認為true。 */ private boolean flushCacheRequired; /** * 將其設置為 true 后,將會導致本條語句的結果被二級緩存緩存起來,默認值:對 select 元素為 true。 */ private boolean useCache; /** * sql命令類型:如select、update、insert、delete等 */ private SqlCommandType sqlCommandType; //…… /** * 語言驅動,如xml。 */ private LanguageDriver lang; /** * 結果集類型列表 */ private String[] resultSets; } 復制代碼 ~~~ ### MappedStatement是怎么來的? 還是以XML配置方式為例進行分析,簡單說下源碼查找的過程。Mapper對應的SQL語句定義在xml文件中,順著源碼會發現完成xml解析工作的是XMLMapperBuilder,其中對xml中“select|insert|update|delete”類型元素的解析方法為buildStatementFromContext;buildStatementFromContext使用了XMLStatementBuilder類對statement進行解析,并最終創建了MappedStatement。 所以,XMLStatementBuilder#parseStatementNode方法就是我們分析的重點。但是,在此之前需要有一點準備工作要做。由于MappedStatement最終是由MapperBuilderAssistant構建的,它其中存儲了一些Mapper級別的共享信息并應用到MappedStatement中。所以,先來簡單了解下它的由來: ~~~java public class XMLMapperBuilder extends BaseBuilder { private final XPathParser parser; private final MapperBuilderAssistant builderAssistant; //省略部分字段和方法 public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) { this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()), configuration, resource, sqlFragments); } private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) { super(configuration); this.builderAssistant = new MapperBuilderAssistant(configuration, resource); this.parser = parser; this.sqlFragments = sqlFragments; this.resource = resource; } //省略部分字段和方法 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); //省略部分代碼 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); } } 復制代碼 ~~~ * 代碼14行:MapperBuilderAssistant由XMLMapperBuilder的私有構造方法創建,這里傳入了全局Configuration對象和xml資源文件路徑; * 代碼24~28行:設置builderAssistant的namespace。這個namespace就是我們在mapper xml中聲明的,也就是我們Mapper接口的完全限定名,如com.raysonxin.dao.CompanyDao。 * 代碼30行:開始了聲明節點的解析。 好了,下面正是開始XMLStatementBuilder#parseStatementNode的分析了。**為了節省篇幅,我直接通過代碼注釋的方式進行說明了,部分我認為不關鍵或不常用的內容沒有多說**。 ~~~java /** * parseStatementNode方法是對select、insert、update、delete這四類元素進行解析,大體分為三個過程: * 1、解析節點屬性:如我們最常用的id、resultMap等; * 2、解析節點內的sql語句:首先把sql語句中包含的<include></include>等標簽轉為實際的sql語句,然后執行靜態或動態節點處理; * 3、根據以上解析到的內容,使用builderAssistant創建MappedStatement,并加入Configuration中。 * <p> * 以上過程中最關鍵的是第二步,它會根據實際使用的標簽,把sql片段轉為不同的SqlNode,以鏈表方式存儲到SqlSource中。 */ public void parseStatementNode() { //獲取標簽的id屬性,如selectById,對應Mapper接口中的方法名稱 String id = context.getStringAttribute("id"); //獲取databaseId屬性,我們一般都沒有寫。 String databaseId = context.getStringAttribute("databaseId"); /** * 這段代碼雖然不起眼,但是一定要進去看一下:其內部完成了對id的再次賦值, * 處理的方式是:id=namespace+"."+id,也就是當前Mapper的完全限定名+"."+id, * 比如我們之前例子中的com.raysonxin.dao.CompanyDao.selectById * 這也是Mapper接口中不能存在重載方法的根本原因。 * */ if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) { return; } /** * 下面這塊代碼會依次獲取fetchSize、timeout、resultMap等屬性, * 需要注意的是,有些屬性雖然我們沒有設置,但是mybatis會設置默認值, * 具體可以查看mybatis的官方說明。 */ 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"); //默認值:XMLLanguageDriver LanguageDriver langDriver = getLanguageDriver(lang); Class<?> resultTypeClass = resolveClass(resultType); String resultSetType = context.getStringAttribute("resultSetType"); //默認值:PREPARED StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString())); //默認值:DEFAULT ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); String nodeName = context.getNode().getNodeName(); 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); // Include Fragments before parsing /** * 英文注釋也說了,在sql解析前處理 include 標簽,比如說,我們include了BaseColumns, * 它會把這個include標簽替換為BaseColumns內的sql內容 * */ XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant); includeParser.applyIncludes(context.getNode()); // Parse selectKey after includes and remove them. //處理selectKey,主要針對不同的數據庫引擎做處理 processSelectKeyNodes(id, parameterTypeClass, langDriver); // Parse the SQL (pre: <selectKey> and <include> were parsed and removed) /** * 到了關鍵步驟了:就是通過這句代碼完成了從xml標簽到SqlSource的轉換, * SqlSource是一個接口,這里返回的可能是DynamicSqlSource、也可能是RawSqlSource, * 取決于xml標簽中是否包含動態元素,比如 <if test=""></if> * */ SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); String resultSets = context.getStringAttribute("resultSets"); //下面這些是針對selectKey、KeyGenerator等進行處理,暫時跳過了。 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; } /** * 節點及屬性都解析完成了,使用builderAssistant創建MappedStatement, * 并保存到Configuration#mappedStatements。 * */ builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); } 復制代碼 ~~~ 我們在xml中定義的select等語句就是通過這個parseStatementNode方法解析為MappedStatement的,整體來看比較容易理解,核心就是SqlSource的創建過程,大家可以寫個簡單的例子一步一步調試看下。 ### SqlSource是什么,如何創建的? SqlSource是整個MappedStatement的核心,MappedStatement其他一大堆字段都是為了準確的執行它而定義的。SqlSource是個半成品的sql語句,因為對于其中的動態標簽還沒靜態化,其中的參數也未賦值。正是如此,才為我們后續的調用執行提供了基礎,接下來重點看看SqlSource的構建過程。為了先從整體上了解,我畫了一個時序圖來描述SqlSource的解析、創建過程。 ![image.png](//p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/89dccb3ac8cb484cbd1d63943028e531~tplv-k3u1fbpfcp-zoom-1.image) 這個過程涉及三個參與者XMLStatementBuilder、XMLLanguageDriver、XMLScriptBuilder,核心在于XMLScriptBuilder對標簽的識別與解析,我們重點看XMLScriptBuilder#parseScriptNode這個方法,如下: ~~~java /** * parseScriptNode字面意思,解析sql腳本節點。 * */ public SqlSource parseScriptNode() { // parseDynamicTags處理節點中的動態標簽,其實動態、靜態標簽都會讀取, // 只是對于動態標簽會使用對應的動態標簽處理器解析。 MixedSqlNode rootSqlNode = parseDynamicTags(context); SqlSource sqlSource = null; // 如果是動態的,創建DynamicSqlSource;否則創建RawSqlSource。 // isDynamic默認是false,parseDynamicTags處理中,只要存在動態元素,他就被置為true if (isDynamic) { sqlSource = new DynamicSqlSource(configuration, rootSqlNode); } else { sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType); } return sqlSource; } 復制代碼 ~~~ 剛才一直再說SqlNode,那SqlNode到底是什么呢?結合一個例子,我們先簡單認識一下它,同樣放一張類圖(圖中并沒有放全)來了解其家族。 ![image.png](//p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b20e5d85a2274105bc7f649c6cc68a7f~tplv-k3u1fbpfcp-zoom-1.image) ~~~xml <select id="selectById" resultMap="baseResultMap" > select <include refid="BaseColumns"></include> from company <if test="id != null"> where id= #{id} </if> </select> 復制代碼 ~~~ 對于這個示例中的select標簽的sql語句(只看2-7行),mybatis會按照標簽完整性(閉合)解析為多個節點,同時根據節點中出現的元素類型創建不同類型的節點(依據是《Document Object Model (DOM) Level 3 Core Specification》中定義的12中類型),比如例子中,純文本解析為TextSqlNode,if標簽解析為IfSqlNode。 但是,我們在編寫sql語句時大多數情況是多種類型混合的,所以就有了MixedSqlNode,它以List存儲了所有的節點。parseDynamicTags方法的作用就是解析sql語句中的節點類型,并最終生成MixedSqlNode。 ~~~java protected MixedSqlNode parseDynamicTags(XNode node) { List<SqlNode> contents = new ArrayList<>(); NodeList children = node.getNode().getChildNodes(); //遍歷節點 for (int i = 0; i < children.getLength(); i++) { //獲取當前節點 XNode child = node.newXNode(children.item(i)); //判斷節點類型是否為CDATA_SECTION_NODE或TEXT_NODE if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) { //獲取節點的sql語句內容 String data = child.getStringBody(""); //創建TextSqlNode TextSqlNode textSqlNode = new TextSqlNode(data); //判斷是否為動態節點,實際判斷是否包含${}占位符,有就是true if (textSqlNode.isDynamic()) { contents.add(textSqlNode); //設置為動態sql isDynamic = true; } else { //不是動態節點,創建StaticTextSqlNode contents.add(new StaticTextSqlNode(data)); } } //節點類型是否為ELEMENT_NODE else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628 //獲取節點的名稱,比如if String nodeName = child.getNode().getNodeName(); //獲取對應的處理器,如IfHandler XMLScriptBuilder.NodeHandler handler = nodeHandlerMap.get(nodeName); if (handler == null) { throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement."); } //調用處理器,解析動態節點內容,這里面也會遞歸調用parseDynamicTags,逐層處理。 handler.handleNode(child, contents); //設置為動態sql isDynamic = true; } } //創建MixedSqlNode return new MixedSqlNode(contents); } 復制代碼 ~~~ 總結一下parseDynamicTags方法的處理過程,parseDynamicTags僅處理類型為CDATA\_SECTION\_NODE、TEXT\_NODE、ELEMENT\_NODE的節點。 * 前兩種類型會首先作為TextSqlNode,若節點中沒有占位符`${}`,則轉為StaticTextSqlNode;若節點中有占位符`${}`,節點類型為TextSqlNode不變,但是會將這條sql設置為dynamic。這會導致后續的參數設置方式不同,引出`#{}`、`${}`的差別,我們在下一節在說明。 * 對ELEMENT\_NODE節點,會根據節點名稱(if、choose等)找到對應的處理器解析為動態節點,處理器有IfHandler、WhereHandler等9種。 所以,回到XMLScriptBuilder#parseScriptNode方法,根據isDynamic的值,會創建不同類型的SqlSource。到目前為止,我們知道創建DynamicSqlSource有兩種情況:一是sql節點包含if、choose、where這里動態標簽時;二是使用了`${}`占位符時。其余情況會創建RawSqlSource。 好了,SqlSource的創建過程我們就分析完了,在翻上去看看那張時序圖加深一下印象吧。 SqlSource創建完成后,就剩下builderAssistant#addMappedStatement這個過程了,比較簡單,大家可以自己查看源碼了解一下,我就不廢話了。 ### 本文總結 正如文章開頭所說,本文的主要目的是打基礎,弄清楚是什么,為什么,為以后的怎么做做好鋪墊。本文分析了MappedStatement主要字段及作用、Mybatis如何創建MappedStatement、MappedStatement中的SqlSource是什么及創建過程幾個問題,雖然寫的很啰嗦,但是問題應該是描述的差不多了,希望能對大家的理解有一些幫助。 作者:碼路印記 鏈接:https://juejin.cn/post/6886805132936216583 來源:掘金 著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看