<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>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                Mybatis參數設置過程分為參數解析處理、動態SQL轉為靜態SQL、預編譯參數賦值幾個步驟,其中動態SQL轉為靜態SQL的過程中會涉及預編譯`${}`參數賦值和預編譯參數的解析工作。本文將從一個簡單的示例出發了解Mybatis參數設置的全部流程,文章會涉及一些常見的問題: * `${}`占位符的處理流程; * `#{}`占位符的處理流程; * ParameterHandler#setParameters的執行流程; ### 從Demo開始 這個demo是在原來的基礎上進行了改造,不過也僅僅是mapper.xml部分,大家先有個簡單的印象,方便后續文章進行分析。 * CompanyMapper.xml xml中僅有一個select查詢語句,為了便于分析`${}`與`#{}`兩種占位符的差別,我在一個語句中分別使用了兩種不同的方式來設置參數id。 ~~~xml <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.raysonxin.dao.CompanyDao"> <resultMap id="baseResultMap" type="com.raysonxin.dataobject.CompanyDO"> <id column="id" property="id"/> <result column="name" property="name"/> <result column="cpy_type" property="cpyType"/> </resultMap> <select id="selectById" resultMap="baseResultMap"> select id,name,cpy_type from company <where> <if test="id != null"> id = ${id} or id = #{id} </if> </where> </select> </mapper> 復制代碼 ~~~ * CompanyDao.java ~~~java public interface CompanyDao { /** * 根據id查詢CompanyDO * * @param cmpId 主鍵id * @return 查詢結果 */ CompanyDO selectById(@Param("id") Integer cmpId); } 復制代碼 ~~~ * Program.java ~~~java public class Program { public static void main(String[] args) throws IOException { // mybatis配置文件 String path = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(path); // 獲取 SqlSessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 創建 SqlSession try (SqlSession sqlSession = sqlSessionFactory.openSession()) { CompanyDao dao = sqlSession.getMapper(CompanyDao.class); CompanyDO companyDO = dao.selectById(1); System.out.println(companyDO); } } } 復制代碼 ~~~ 下圖來自之前的文章[《Mybatis源碼之SQL執行過程》](https://mp.weixin.qq.com/s/XPxWxvpffQCXyELjcNH6lA),經過前文的分析,我們知道Mapper接口的方法是通過動態代理的方式由代理類MapperProxy委托到SqlSession對應的接口執行的,MapperProxy#invoke才是Mapper方法的真正入口,而**本文所要分析的參數設置過程也是從這里開始的**(確切來講是MapperProxy#invoke方法所調用的MapperMethod#execute方法)。 ### ![image.png](//p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a35ec2748e564edf97415ab606eb0ae1~tplv-k3u1fbpfcp-zoom-1.image) Mybatis的參數設置流程,與我們常規意義上調用方法的參數設置有些不同。因為我們調用Mapper接口的方法,其參數雖然在接口中有定義,但是實際是作用在SQL語句上,而SQL語句是通過占位符(**`${}`與`#{}`**)來預占參數的。 **這里其實就可能存在一定的差異,Mapper接口方法所定義的參數名稱、個數、類型、順序等一定與SQL語句的要求匹配嗎?如果匹配,那Mybatis是如何把我們給定的參數,按照預期為SQL語句設置參數的呢?帶著這幾個問題,我們開始Mybatis參數設置流程的分析!** ### 參數解析 由于Mapper接口方法執行時調用的是MapperMethod,其執行依賴MappedStatement中SqlSource對參數的要求。因此,在方法執行之前,Mybatis需要先把Mapper接口參數名稱解析為通用的、可被理解的方式;同時,引入一些特性支持開發人員的參數定制。 結合示例代碼,我們使用的是select語句,也就是會執行SELECT命令,MapperMethod#execute僅貼出與SELECT有關的代碼進行說明。 ~~~java public Object execute(SqlSession sqlSession, Object[] args) { Object result; switch (command.getType()) { //省略INSERT、UPDATE、DELTE、FLUSH…… //僅查看SELECT命令 case SELECT: //無返回值或者具有ResultHandler if (method.returnsVoid() && method.hasResultHandler()) { executeWithResultHandler(sqlSession, args); result = null; } //返回列表 else if (method.returnsMany()) { result = executeForMany(sqlSession, args); } //返回Map else if (method.returnsMap()) { result = executeForMap(sqlSession, args); } //返回Cursor else if (method.returnsCursor()) { result = executeForCursor(sqlSession, args); } //返回單個結果 else { //參數轉換:把參數轉為sql命令參數 Object param = method.convertArgsToSqlCommandParam(args); //調用SqlSession#selectOne方法 result = sqlSession.selectOne(command.getName(), param); if (method.returnsOptional() && (result == null || !method.getReturnType().equals(result.getClass()))) { result = Optional.ofNullable(result); } } break; } if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) { throw new BindingException("Mapper method '" + command.getName() + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ")."); } return result; } 復制代碼 ~~~ 在SELECT命令最后一個else的代碼塊(第27行),調用了MethodSignature#convertArgsToSqlCommandParam方法,作用是把我們通過Mapper接口傳入的參數轉為SQL命令參數。先通過斷點看下方法執行后的效果: ![image.png](data:image/svg+xml;utf8,) 示例中我們給接口selectById傳入的參數為id=1(但是參數名id其實是不存在的),經過這個方法的處理,我們得到了一個名為param的ParamMap對象,其中包含兩個鍵值對:id=1,param1=1;接著就把param交給了SqlSession#selectOne方法去執行。先不要著急想為什么這么轉換,我們先來了解下轉換的過程(我已經貼心的為繪制了這一過程的時序圖)。 ![image.png](data:image/svg+xml;utf8,) 跟著代碼走,我們會一步步走到ParamNameResolver#getNamedParams,在此之前已經在MapperMethod構造方法中完成了ParamNameResolver的實例化,所以我們先看下ParamNameResolver的構造方法,然后再了解getNamedParams的實現過程。(***同一個類,代碼放在一起了,省略部分無關方法***)。 ~~~java public class ParamNameResolver { //通用參數名稱前綴 private static final String GENERIC_NAME_PREFIX = "param"; /** * <p> * The key is the index and the value is the name of the parameter. * The name is obtained from {@link Param} if specified. When {@link Param} is not specified, * the parameter index is used. Note that this index could be different from the actual index * when the method has special parameters (i.e. {@link RowBounds} or {@link ResultHandler}). * </p> * <ul> * <li>aMethod(@Param("M") int a, @Param("N") int b) -&gt; {{0, "M"}, {1, "N"}}</li> * <li>aMethod(int a, int b) -&gt; {{0, "0"}, {1, "1"}}</li> * <li>aMethod(int a, RowBounds rb, int b) -&gt; {{0, "0"}, {2, "1"}}</li> * </ul> */ private final SortedMap<Integer, String> names; private boolean hasParamAnnotation; public ParamNameResolver(Configuration config, Method method) { // 獲取方法的參數類型,示例中即Integer final Class<?>[] paramTypes = method.getParameterTypes(); // 獲取方法參數的@Param注解列表 final Annotation[][] paramAnnotations = method.getParameterAnnotations(); final SortedMap<Integer, String> map = new TreeMap<>(); int paramCount = paramAnnotations.length; // get names from @Param annotations // 從@Param中獲取參數名稱 for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) { // 是否為特殊參數,特殊參數是指RowBounds、ResultHandler if (isSpecialParameter(paramTypes[paramIndex])) { // skip special parameters continue; } String name = null; // 按照參數順序獲取注解 for (Annotation annotation : paramAnnotations[paramIndex]) { //判斷是否為@Param注解 if (annotation instanceof Param) { // 標記具有@Param注解 hasParamAnnotation = true; // 獲取@Param注解中的參數名稱,示例中“cmpId” name = ((Param) annotation).value(); // 獲取到注解中的參數即停止 break; } } // 到這里,說明從注解中未獲取到參數名稱 if (name == null) { // @Param was not specified. // 未定指定@Param,允許使用實際參數名稱,我們示例中的id if (config.isUseActualParamName()) { // 獲取方法中實際參數名稱 name = getActualParamName(method, paramIndex); } // 走到這,就采用參數的索引順序作為名稱,如“0”、“1”。。。 if (name == null) { // use the parameter index as the name ("0", "1", ...) // gcode issue #71 name = String.valueOf(map.size()); } } // 以參數索引順序為key、上述過程獲取到的名稱為value,存儲到map // 示例中:0->id map.put(paramIndex, name); } names = Collections.unmodifiableSortedMap(map); } //…… /** * <p> * A single non-special parameter is returned without a name. * Multiple parameters are named using the naming rule. * In addition to the default names, this method also adds the generic names (param1, param2, * ...). * </p> */ public Object getNamedParams(Object[] args) { final int paramCount = names.size(); // args為null,或者paramCount=0,返回null if (args == null || paramCount == 0) { return null; } // 無@Param注解并且只有一個參數,直接返回args的第一個元素 else if (!hasParamAnnotation && paramCount == 1) { return args[names.firstKey()]; } // 使用@Param注解或有多個參數 else { final Map<String, Object> param = new ParamMap<>(); int i = 0; // 遍歷names for (Map.Entry<Integer, String> entry : names.entrySet()) { // 以names的value為key,以args值為value,添加到param param.put(entry.getValue(), args[entry.getKey()]); // add generic param names (param1, param2, ...) // 添加通用參數,如param1、param2.。。 final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1); // ensure not to overwrite parameter named with @Param // 防止重復,覆蓋已有參數 if (!names.containsValue(genericParamName)) { param.put(genericParamName, args[entry.getKey()]); } i++; } return param; } } } 復制代碼 ~~~ ParamNameResolver構造方法完成了參數名稱的獲取,其中會跳過特殊類型參數,如RowBounds、ResultHandler,最終以k-v方式存儲至SortedMap。參數名稱獲取過程遵循的優先級規則是: * 首先,獲取@Param注解中設置的參數名稱,如示例中的id; * 其次,使用接口方法定義中的參數名稱,如示例中的cmpId; * 最后,使用參數索引編號作為名稱,如“0”、“1”…… getNamedParams方法是為了構造參數名稱與參數值的映射關系,\*\*首先\*\*\*\*把構造方法中獲取到的參數名稱與參數值存儲到map中,然后為增加通用參數名稱與參數值的關系。\*\*這樣,在接口方法簽名中或@Param注解中的參數名稱不與通用參數發生沖突的情況下,每個參數值都會對應兩個key,即上面調試截圖的形式。 雖然占用的篇幅較多,但是參數解析的過程還是比較簡單的,Mybatis結合全局配置,通過反射技術、@Param注解等方式,把接口入參轉為一個Map結構,下一步調用SqlSession相關接口完成執行。 ### 動態SQL轉為靜態SQL【可選】 *這里的動態SQL是指DynamicSqlSource,靜態SQL是指StaticSqlSource。我對這兩者的理解是:* * *DynamicSqlSource:SQL語句存在動態元素,需要在運行時通過參數值才能將其轉換為結構確定的、可以直接賦值的SQL語句,也就是靜態化的SQL;* * *StaticSqlSource:包含形如`select * from tb_test where id=?`這樣的SQL以及參數映射列表,只需要把其中的`?`替換為指定參數即可執行。* 通過[《Mybatis源碼之MappedStatement》](https://mp.weixin.qq.com/s/lbOfdQsiAcrLv1YJsP_vEw)這篇文章,我們可以知道,如果在SQL語句中使用了動態元素(如:if標簽、${}占位符等)MappedStatement#SqlSource類型為DynamicSqlSource,否則為RawSqlSource。其中,RawSqlSource內部其實封裝了一個StaticSqlSource,已經完成了靜態化處理及SQL語句中參數的提取操作。**示例中,我們的SQL語句使用where、if這類標簽,顯然是動態SQL。所以,我們接下來按照動態SQL的流程來分析。** #### 主體流程 “參數解析”一節,代碼調用到了SqlSession#selectOne方法,隨著代碼執行,我們可以走到一個關鍵的方法MappedStatement#getBoundSql,它用來獲取一個BoundSql對象。先看一下這段代碼的執行邏輯: ~~~java public BoundSql getBoundSql(Object parameterObject) { // 通過SqlSource獲取BoundSql對象 BoundSql boundSql = sqlSource.getBoundSql(parameterObject); // 獲取BoundSql中的參數映射列表 List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); //如果parameterMappings為空,則使用xml中的參數映射配置。 if (parameterMappings == null || parameterMappings.isEmpty()) { boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject); } // 一般用不到,省略了 return boundSql; } 復制代碼 ~~~ getBoundSql就是為了獲取BoundSql對象,首先通過SqlSource獲取,如果未獲取到parameterMappings,則考慮使用Configuration(即xml配置文件)中的ParameterMap作為參數映射。我們主要分析通過SqlSource獲取的過程:示例中是動態SQL,所以這里的sqlSource是DynamicSqlSource的實例。 ~~~java public class DynamicSqlSource implements SqlSource { private final Configuration configuration; private final SqlNode rootSqlNode; public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) { this.configuration = configuration; this.rootSqlNode = rootSqlNode; } //org.apache.ibatis.scripting.xmltags.DynamicSqlSource#getBoundSql public BoundSql getBoundSql(Object parameterObject) { // 創建動態上下文對象,它里面主要是我們的從第一節獲取到的參數解析結果 DynamicContext context = new DynamicContext(configuration, parameterObject); // 動態SQL內部的SqlNode是MixedSqlNode實例,由多種不同的SqlNode組成,apply會依次調用SqlNode的apply方法 rootSqlNode.apply(context); SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration); Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass(); // 動態sql的轉換過程就在這里。 SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings()); // 創建BoundSql對象 BoundSql boundSql = sqlSource.getBoundSql(parameterObject); for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) { boundSql.setAdditionalParameter(entry.getKey(), entry.getValue()); } return boundSql; } } 復制代碼 ~~~ * DynamicContext對象存儲了這個階段所需要的參數信息、提供了解析后sql語句拼接的工具方法及并存儲處理后的sql語句,它貫穿SqlSource解析的整個過程。 * rootSqlNode是MixedSqlNode的實例,它內存通過contents字段存儲了SQL語句所包含的SqlNode列表,通過apply方法依次調用各個SqlNode#apply方法,完成SqlNode中表達式的計算及結果輸出。例如:通過計算表達式的結果,決定標簽內的SQL是否滿足執行條件。由于SqlNode的類型有多種,處理方式也不盡相同,具體類型需要具體分析。 * SqlSourceBuilder顧名思義,就是用來做SqlSource構建的工具類,具體來說是StaticSqlSource的構建類。正是通過它完成了從動態到靜態的處理過程,期間會提取出SQL語句中通過`#{}`占位符預設的參數映射信息。 DynamicSqlSource#getBoundSql方法的執行過程就比較清晰了,整體分為兩個步驟:根據輸入參數把MixedSqlNode中各個子SqlNode靜態化,然后解析SQL語句中的`#{}`占位符所定義的參數映射信息并以`?`替換,創建StaticSqlSource對象和BoundSql。先通過下圖概括這個階段的執行過程,其中黃色注釋部分是需要具體展開的。 ![image.png](data:image/svg+xml;utf8,) #### MixedSqlNode#apply MixedSqlNode代碼如下所示,contents存儲SQL所包含的所有SqlNode,apply方法執行時,會依次把MixedSqlNode#contents中的SqlNode遍歷并調用apply方法。 ~~~java // MixedSqlNode public class MixedSqlNode implements SqlNode { private final List<SqlNode> contents; public MixedSqlNode(List<SqlNode> contents) { this.contents = contents; } @Override public boolean apply(DynamicContext context) { for (SqlNode sqlNode : contents) { sqlNode.apply(context); } return true; } } 復制代碼 ~~~ 示例中的contents如下圖所示,有兩個StaticTextSqlNode、一個IfSqlNode(它內部存儲的也是一個MixedSqlNode,包含一個TextSqlNode)。 ![image.png](data:image/svg+xml;utf8,) MixedSqlNode#apply方法的分析過程就比較容易理解,只需要依次看下StaticTextSqlNode、IfSqlNode、TextSqlNode對apply方法的實現過程即可。 * StaticTextSqlNode:靜態文本,沒有動態元素和參數處理,僅僅是拼接sql語句; ~~~java //org.apache.ibatis.scripting.xmltags.StaticTextSqlNode#apply public boolean apply(DynamicContext context) { context.appendSql(text); return true; } // org.apache.ibatis.scripting.xmltags.DynamicContext#appendSql public void appendSql(String sql) { sqlBuilder.append(sql); sqlBuilder.append(" "); } 復制代碼 ~~~ * IfSqlNode:涉及ONGL表達式的判斷,當表達式成立時,再調用IfSqlNode中的SqlNode#apply方法。 ~~~java //org.apache.ibatis.scripting.xmltags.IfSqlNode#apply public boolean apply(DynamicContext context) { //表達式成立繼續執行。 if (evaluator.evaluateBoolean(test, context.getBindings())) { contents.apply(context); return true; } return false; } 復制代碼 ~~~ * 此時,SqlNode的類型是MixedSqlNode,跟上面的邏輯類似,所以會執行到TextSqlNode。 \*\* \*\*TextSqlNode#apply\*\*\*\*雖然代碼只有兩行,\*\***這個過程比較復雜一些,涉及到了占位符`${}`的解析過程,我們重點看下。代碼如下:** ~~~java // org.apache.ibatis.scripting.xmltags.TextSqlNode#apply public boolean apply(DynamicContext context) { // 創建${}占位符的解析器,其中GenericTokenParser是通用的,BindingTokenParser是對占位符的替換方式實現 GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter)); //解析${},完成替換,把解析結果拼接到sql語句中 context.appendSql(parser.parse(text)); return true; } private GenericTokenParser createParser(TokenHandler handler) { //創建GenericTokenParser,處理的目標是`${`和`}`開閉包含的內容 return new GenericTokenParser("${", "}", handler); } 復制代碼 ~~~ GenericTokenParser是一個通用的SQL語句占位符的解析類,會從頭到尾依次匹配openToken(如:`${`、`#{`)和closeToken(如:`}`)中間包含的表達式。若匹配到表達式,就會通過TokenHandler接口對象,對匹配表達式進行處理。通過上述代碼可以,當處理`${}`時使用的是BindingTokenParser。 示例中,GenericTokenParser#parse方法的入參text是(如上圖TextSqlNode的內容): ~~~sql id = ${id} or id = #{id} 復制代碼 ~~~ GenericTokenParser的parse方法比較長,但是很簡單,我們重點來看下當匹配到表達式的處理流程,即BindingTokenParser#handleToken方法: ~~~java private static class BindingTokenParser implements TokenHandler { // 上下文對象,內容包含了參數及sql語句解析結果 private DynamicContext context; private Pattern injectionFilter; public BindingTokenParser(DynamicContext context, Pattern injectionFilter) { this.context = context; this.injectionFilter = injectionFilter; } @Override // 入參content是GenericTokenParser匹配到的表達式,示例中為“id” public String handleToken(String content) { // 獲取輸入的參數對象。示例為ParamMap,包含:"id"->1,"param1"->1 Object parameter = context.getBindings().get("_parameter"); if (parameter == null) { context.getBindings().put("value", null); } else if (SimpleTypeRegistry.isSimpleType(parameter.getClass())) { context.getBindings().put("value", parameter); } //通過ongl獲取表達式對應的值,這里獲取的value=1。 Object value = OgnlCache.getValue(content, context.getBindings()); String srtValue = value == null ? "" : String.valueOf(value); // issue #274 return "" instead of "null" //sql入侵檢測 checkInjection(srtValue); return srtValue; } private void checkInjection(String value) { if (injectionFilter != null && !injectionFilter.matcher(value).matches()) { throw new ScriptingException("Invalid input. Please conform to regex" + injectionFilter.pattern()); } } } 復制代碼 ~~~ 代碼走下來,我們知道BindingTokenParser#handleToken方法是完成了對GenericTokenParser#parse匹配到表達式的替換,即:由`id`替換為參數對應的值1。所以,原來SQL語句前后變化如下: ~~~sql # GenericTokenParser#parse入參 id = ${id} or id = #{id} # GenericTokenParser#parse輸出結果 id = 1 or id = #{id} 復制代碼 ~~~ 以上處理結果會保存在DynamicContext中,MixedSqlNode#apply也就執行完成了。MixedSqlNode#contents中的SqlNode列表也被處理為了一條比較簡潔的SQL語句,如下: ~~~sql select id,name,cpy_type from company WHERE id = 1 or id = #{id} 復制代碼 ~~~ #### SqlSourceBuilder#parse 剛剛有說到,SqlSourceBuilder#parse的作用是匹配SQL語句中的`#{}`占位符,并將匹配到的表達式解析為參數映射列表。我們通過代碼了解下過程: ~~~java //org.apache.ibatis.builder.SqlSourceBuilder#parse public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) { ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters); GenericTokenParser parser = new GenericTokenParser("#{", "}", handler); String sql = parser.parse(originalSql); return new StaticSqlSource(configuration, sql, handler.getParameterMappings()); } 復制代碼 ~~~ 相信大家一眼就看出來了,SqlSourceBuilder#parse的處理過程與TextSqlNode#apply的過程如出一轍,區別就在于SqlSourceBuilder#parse處理的是`#{}`,而且使用了ParameterMappingTokenHandler這個TokenHandler實現類。所以,我們只需要了解ParameterMappingTokenHandler#handleToken處理過程即可: ~~~java public String handleToken(String content) { parameterMappings.add(buildParameterMapping(content)); return "?"; } 復制代碼 ~~~ handleToken通過buildParameterMapping方法創建了ParameterMapping(參數映射)并添加到parameterMappings參數映射列表中;同時返回了`?`用于替換原來GenericTokenParser匹配到的表達式。也就是我們常說的,`#{}`內的參數會被`?`替換。 **buildParameterMapping方法比較長,也比較復雜,我現在也沒有完全理解,其中的case也沒有遇到過,等以后理解了再來補充**。雖然未完全理解,但不影響我們對整個過程的把握。在我們的例子中,它會使用構造器ParameterMapping.Builder把表達式轉為ParameterMapping,其中會尋找參數映射所需的TypeHandler,用于后續的參數處理。 動態SQL轉為靜態SQL這個過程就分析完了,其實主要就是兩件事:一是ONGL通過輸入參數把SqlNode中的SQL片段轉為靜態的SQL,二是解析處理SQL語句中的參數占位符`${}`和`#{}`,Mybatis對二者的處理有很大的不同。對于`${}`直接將表達式替換為對應的參數值,對于`#{}`提取參數信息轉為ParameterMapping,并將原來的表示是替換為`?`。 ### 預編譯參數賦值 終于來到最后一個環節!獲得BoundSql之后,回到Mybatis對SQL的執行流程,就會從CachingExecutor一直走到PreparedStatementHandler#parameterize方法,它會使用DefaultParameterHandler對預編譯SQL執行參數賦值,它是ParameterHandler的默認實現。當方法運行到這里后,DefaultParameterHandler的關鍵字段情況如下圖所示: ![image.png](data:image/svg+xml;utf8,) 結合上圖中boundSql等字段的信息,我們看下setParameters方法的執行流程: ~~~java public void setParameters(PreparedStatement ps) { ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId()); // 通過BoundSql獲取參數映射列表 List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); // 僅參數映射列表非空時處理 if (parameterMappings != null) { // 遍歷參數映射列表 for (int i = 0; i < parameterMappings.size(); i++) { // 獲取當前參數映射 ParameterMapping parameterMapping = parameterMappings.get(i); // 僅參數類型不是OUT時處理,示例為IN if (parameterMapping.getMode() != ParameterMode.OUT) { Object value; // 獲取參數名稱 String propertyName = parameterMapping.getProperty(); // 額外參數是否包含當前屬性,當前是_parameter和_databaseId if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params value = boundSql.getAdditionalParameter(propertyName); } else if (parameterObject == null) { value = null; } // 類型處理器是否包含參數類型,當前參數類型為ParamMap else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { value = parameterObject; } else { // 創建MetaObject對象 MetaObject metaObject = configuration.newMetaObject(parameterObject); // 獲取屬性對應的值 value = metaObject.getValue(propertyName); } // 獲取參數映射的類型處理器。這里是UnknownTypeHandler TypeHandler typeHandler = parameterMapping.getTypeHandler(); // 獲取jdbcType JdbcType jdbcType = parameterMapping.getJdbcType(); if (value == null && jdbcType == null) { jdbcType = configuration.getJdbcTypeForNull(); } try { //通過類型處理器為ps參數賦值 typeHandler.setParameter(ps, i + 1, value, jdbcType); } catch (TypeException e) { throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e); } catch (SQLException e) { throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e); } } } } } 復制代碼 ~~~ setParameters方法的執行過程比較清晰:依次遍歷參數映射列表,從parameterObject中獲取參數對應的值,然后通過類型處理器為PreparedStatement指定索引位置的參數賦值,最終完成所有參數的賦值操作。 這里需要注意的是,在通過TypeHandler為PreparedStatement賦值時,Mybatis會通過類型處理器注冊中心查找與當前參數匹配的TypeHandler。因為我們的ParameterMapping是通過對SQL語句中預編譯占位符`#{}`的解析而得到的,這里并沒有明確參數的確切類型,Mybatis為ParameterMapping默認了UnknownTypeHandler作為處理器,即把參數類型默認為Object。所以,在我們的示例中會首先通過UnknownTypeHandler,然后通過resolveTypeHandler找到正確的類型處理器IntegerTypeHandler為PreparedStatement完成賦值操作。 ### 總結 Mybatis參數設置過程到這里就完成了,非常感謝你能耐心的讀到這里!簡單做個總結吧:Mybatis的參數設置過程其實不僅僅是參數設置那么簡單,它涉及了Mybatis對入參的轉換處理,MappedStatement對參數的要求,而且對不同的占位符采取了不同的處理方式,以滿足我們多種情景的要求;最終通過DefaultParameterHandler完成了對預編譯聲明PreparedStatement的參數賦值。 我們的示例是比較簡單的,僅僅包含了一個參數,而且未涉及這種復雜的標簽,你也可以打開源碼去了解下。 作者:碼路印記 鏈接:https://juejin.cn/post/6888957681492492295 來源:掘金 著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
                  <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>

                              哎呀哎呀视频在线观看