最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

源碼解讀Mybatis占位符#和$的區(qū)別

 更新時間:2023年02月10日 09:00:06   作者:wayn111  
這篇文章主要介紹了Mybatis占位符#和$的區(qū)別通過源碼解讀,針對筆者日常開發(fā)中對?Mybatis?占位符?#{}?和?${}?使用時機結合源碼,思考總結而來,需要的朋友可以參考下

Mybatis 作為國內開發(fā)中常用到的半自動 orm 框架,相信大家都很熟悉,它提供了簡單靈活的xml映射配置,方便開發(fā)人員編寫簡單、復雜SQL,在國內互聯(lián)網公司使用眾多。

本文針對筆者日常開發(fā)中對 Mybatis 占位符 #{}${} 使用時機結合源碼,思考總結而來

  • Mybatis 版本 3.5.11
  • Spring boot 版本 3.0.2
  • mybatis-spring 版本 3.0.1
  • github地址:https://github.com/wayn111 歡迎大家關注,點個star

一. 啟動時,mybatis-spring 解析xml文件流程圖

Spring 項目啟動時,mybatis-spring 自動初始化解析xml文件核心流程

MybatisbuildSqlSessionFactory() 會遍歷所有 mapperLocations(xml文件) 調用 xmlMapperBuilder.parse()解析,源碼如下

在 parse() 方法中, Mybatis 通過 configurationElement(parser.evalNode("/mapper")) 方法解析xml文件中的各個標簽

public class XMLMapperBuilder extends BaseBuilder {
  ...
  private final MapperBuilderAssistant builderAssistant;
  private final Map<String, XNode> sqlFragments;
  ...
  
    public void parse() {
      if (!configuration.isResourceLoaded(resource)) {
        // xml文件解析邏輯
        configurationElement(parser.evalNode("/mapper"));
        configuration.addLoadedResource(resource);
        bindMapperForNamespace();
      }

      parsePendingResultMaps();
      parsePendingCacheRefs();
      parsePendingStatements();
    }

    private void configurationElement(XNode context) {
      try {
        // 解析xml文件內的namespace、cache-ref、cache、parameterMap、resultMap、sql、select、insert、update、delete等各種標簽
        String namespace = context.getStringAttribute("namespace");
        if (namespace == null || namespace.isEmpty()) {
          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"));
        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);
      }
    }
}

最后會把 namespace、cache-ref、cache、parameterMap、resultMap、select、insert、update、delete等標簽內容解析結果放到 builderAssistant 對象中,將sql標簽解析結果放到sqlFragments對象中,其中 由于 builderAssistant 對象會保存select、insert、update、delete標簽內容解析結果我們對 builderAssistant 對象進行深入了解

public class MapperBuilderAssistant extends BaseBuilder {
...
}

public abstract class BaseBuilder {
  protected final Configuration configuration;
  ...
}  

public class Configuration {
  ...
  protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
      .conflictMessageProducer((savedValue, targetValue) ->
          ". please check " + savedValue.getResource() + " and " + targetValue.getResource());
  protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");
  protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
  protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");
  protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection");
  protected final Set<String> loadedResources = new HashSet<>();
  protected final Map<String, XNode> sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers");
  ...
}

builderAssistant 對象繼承至 BaseBuilder,BaseBuilder 類中包含一個 configuration 對象屬性, configuration 對象中會保存xml文件標簽解析結果至自身對應屬性mappedStatements、caches、resultMaps、sqlFragments。

這里有個問題上面提到的sql標簽結果會放到 XMLMapperBuilder 類的 sqlFragments 對象中,為什么 Configuration 類中也有個 sqlFragments 屬性?

這里回看上文 buildSqlSessionFactory() 方法最后

原來 XMLMapperBuilder 類中的 sqlFragments 屬性就來自Configuration類??

回到主題,在 buildStatementFromContext(context.evalNodes("select|insert|update|delete")) 方法中會通過如下調用

buildStatementFromContext(List<XNode> list, String requiredDatabaseId) 
-> parseStatementNode()
-> createSqlSource(Configuration configuration, XNode script, Class<?> parameterType)
-> parseScriptNode()
-> parseDynamicTags(context)

最后通過parseDynamicTags(context) 方法解析 select、insert、update、delete 標簽內容將結果保存在 MixedSqlNode 對象中的 SqlNode 集合中

public class MixedSqlNode implements SqlNode {
  private final List<SqlNode> contents;

  public MixedSqlNode(List<SqlNode> contents) {
    this.contents = contents;
  }

  @Override
  public boolean apply(DynamicContext context) {
    contents.forEach(node -> node.apply(context));
    return true;
  }
}

SqlNode 是一個接口,有10個實現(xiàn)類如下

可以看出我們的 select、insert、update、delete 標簽中包含的各個文本(包含占位符 #{} 和 ${})、子標簽都有對應的 SqlNode 實現(xiàn)類,后續(xù)運行中, Mybatis 對于 select、insert、update、delete 標簽的 sql 語句處理都與這里的 SqlNode 各個實現(xiàn)類相關。自此我們 mybatis-spring 初始化流程中相關的重要代碼都過了一遍。

二. 運行中,sql語句占位符 #{} 和 ${} 的處理

這里直接給出xml文件查詢方法標簽內容

<select id="findNewBeeMallOrderList" parameterType="Map" resultMap="BaseResultMap">
    select
    <include refid="Base_Column_List"/>
    from tb_newbee_mall_order
    <where>
        <if test="orderNo!=null and orderNo!=''">
            and order_no = #{orderNo}
        </if>
        <if test="userId!=null and userId!=''">
            and user_id = #{userId}
        </if>
        <if test="payType!=null and payType!=''">
            and pay_type = #{payType}
        </if>
        <if test="orderStatus!=null and orderStatus!=''">
            and order_status = #{orderStatus}
        </if>
        <if test="isDeleted!=null and isDeleted!=''">
            and is_deleted = #{isDeleted}
        </if>
        <if test="startTime != null and startTime.trim() != ''">
            and create_time &gt; #{startTime}
        </if>
        <if test="endTime != null and endTime.trim() != ''">
            and create_time &lt; #{endTime}
        </if>
    </where>
    <if test="sortField!=null and order!=null">
        order by ${sortField} ${order}
    </if>
    <if test="start!=null and limit!=null">
        limit #{start},#{limit}
    </if>
</select>

運行時 Mybatis 動態(tài)代理 MapperProxy 對象的調用流程,如下:

-> newBeeMallOrderMapper.findNewBeeMallOrderList(pageUtil);
-> MapperProxy.invoke(Object proxy, Method method, Object[] args)
-> MapperProxy.invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession)
-> MapperMethod.execute(SqlSession sqlSession, Object[] args)
-> MapperMethod.executeForMany(SqlSession sqlSession, Object[] args)
-> SqlSessionTemplate.selectList(String statement, Object parameter)
-> SqlSessionInterceptor.invoke(Object proxy, Method method, Object[] args)
-> DefaultSqlSession.selectList(String statement, Object parameter)
-> DefaultSqlSession.selectList(String statement, Object parameter, RowBounds rowBounds)
-> DefaultSqlSession.selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler)
-> CachingExecutor.query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler)
-> MappedStatement.getBoundSql(Object parameterObject)
-> DynamicSqlSource.getBoundSql(Object parameterObject)
-> MixedSqlNode.apply(DynamicContext context) // ${} 占位符處理
-> SqlSourceBuilder.parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) // #{} 占位符處理

Mybatis 通過 DynamicSqlSource.getBoundSql(Object parameterObject) 方法對 select、insert、update、delete 標簽內容做 sql 轉換處理,代碼如下:

  @Override
  public BoundSql getBoundSql(Object parameterObject) {
    DynamicContext context = new DynamicContext(configuration, parameterObject);
    rootSqlNode.apply(context);
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    context.getBindings().forEach(boundSql::setAdditionalParameter);
    return boundSql;
  }

2.1 ${} 占位符處理

rootSqlNode.apply(context) -> MixedSqlNode.apply(DynamicContext context) 中會將 SqlNode 集合拼接成實際要執(zhí)行的 sql 語句
保存在 DynamicContext 對象中。這里給出 SqlNode 集合的調試截圖

可以看出我們的 ${} 占位符文本的 SqlNode 實現(xiàn)類為 TextSqlNode,apply方法相關操作如下

public class TextSqlNode implements SqlNode {
    ...
    @Override
    public boolean apply(DynamicContext context) {
      GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter));
      context.appendSql(parser.parse(text));
      return true;
    }
    private GenericTokenParser createParser(TokenHandler handler) {
        return new GenericTokenParser("${", "}", handler);
    }

    // 劃重點,${}占位符替換邏輯在就handleToken(String content)方法中
    @Override
    public String handleToken(String content) {
          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);
          }
          Object value = OgnlCache.getValue(content, context.getBindings());
          String srtValue = value == null ? "" : String.valueOf(value); // issue #274 return "" instead of "null"
          checkInjection(srtValue);
          return srtValue;
    }
}

public class GenericTokenParser {
    public String parse(String text) {
        ...
        do {
            ...
            if (end == -1) {
              ...
            } else {
              builder.append(handler.handleToken(expression.toString()));
              offset = end + closeToken.length();
            }
          }
          ...
        } while (start > -1);
        ...
        return builder.toString();
    }
}   

劃重點,${} 占位符處理如下

handleToken(String content) 方法中, Mybatis 會通過 ognl 表達式將 ${} 的結果直接拼接在 sql 語句中,由此我們得知 ${} 占位符拼接的字段就是我們傳入的原樣字段,有著 Sql 注入風險

2.2 #{} 占位符處理

#{} 占位符文本的 SqlNode 實現(xiàn)類為 StaticTextSqlNode,查看源碼

public class StaticTextSqlNode implements SqlNode {
  private final String text;

  public StaticTextSqlNode(String text) {
    this.text = text;
  }

  @Override
  public boolean apply(DynamicContext context) {
    context.appendSql(text);
    return true;
  }

}

StaticTextSqlNode 會直接將節(jié)點內容拼接在 sql 語句中,也就是說在 rootSqlNode.apply(context) 方法執(zhí)行完畢后,此時的 sql 語句如下

select order_id, order_no, user_id, total_price, 
pay_status, pay_type, pay_time, order_status, 
extra_info, user_name, user_phone, user_address, 
is_deleted, create_time, update_time 
from tb_newbee_mall_order
order by create_time desc
limit #{start},#{limit}

Mybatis 會通過上面提到 getBoundSql(Object parameterObject) 方法中的

sqlSourceParser.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;
  if (configuration.isShrinkWhitespacesInSql()) {
    sql = parser.parse(removeExtraWhitespaces(originalSql));
  } else {
    sql = parser.parse(originalSql);
  }
  return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}

看到了熟悉的 #{ 占位符沒有,哈哈??, Mybatis 對于 #{} 占位符的處理就在 GenericTokenParser類的 parse() 方法中,代碼如下:

public class GenericTokenParser {
    public String parse(String text) {
        ...
        do {
            ...
            if (end == -1) {
              ...
            } else {
              builder.append(handler.handleToken(expression.toString()));
              offset = end + closeToken.length();
            }
          }
          ...
        } while (start > -1);
        ...
        return builder.toString();
    }
}

public class SqlSourceBuilder extends BaseBuilder {
    ... 
    // 劃重點,#{}占位符替換邏輯在就SqlSourceBuilder.handleToken(String content)方法中
    @Override
    public String handleToken(String content) {
      parameterMappings.add(buildParameterMapping(content));
      return "?";
    }
}

劃重點,#{} 占位符處理如下

handleToken(String content) 方法中, Mybatis 會直接將我們的傳入參數(shù)轉換成問號(就是 jdbc 規(guī)范中的問號),也就是說我們的 sql 語句是預處理的。能夠避免 sql 注入問題

三. 總結

由上經過源碼分析,我們知道 Mybatis#{} 占位符是直接轉換成問號,拼接預處理 sql。 ${} 占位符是原樣拼接處理,有sql注入風險,最好避免由客戶端傳入此參數(shù)。

到此這篇關于Mybatis占位符#和$的區(qū)別 源碼解讀的文章就介紹到這了,更多相關Mybatis占位符#和$的區(qū)別內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Java Map 通過 key 或者 value 過濾的實例代碼

    Java Map 通過 key 或者 value 過濾的實例代碼

    這篇文章主要介紹了Java Map 通過 key 或者 value 過濾的實例代碼,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下
    2018-06-06
  • 詳解SpringBoot如何統(tǒng)一處理返回的信息

    詳解SpringBoot如何統(tǒng)一處理返回的信息

    現(xiàn)在的項目是前后端開發(fā)的居多,那么我們怎么定義接口返回的數(shù)據,怎么使用?Spring?Boot?來統(tǒng)一處理返回的信息呢,本文就來和大家簡單講講
    2023-06-06
  • RSA加密的方式和解密方式實現(xiàn)方法(推薦)

    RSA加密的方式和解密方式實現(xiàn)方法(推薦)

    下面小編就為大家?guī)硪黄猂SA加密的方式和解密方式實現(xiàn)方法(推薦)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-06-06
  • Java 深入淺出講解泛型與包裝類

    Java 深入淺出講解泛型與包裝類

    泛型是在Java SE 1.5引入的的新特性,本質是參數(shù)化類型,也就是說所操作的數(shù)據類型被指定為一個參數(shù)。這種參數(shù)類型可以用在類、接口和方法的創(chuàng)建中,分別稱為泛型類、泛型接口、泛型方法,本篇我們一起來學習泛型以及包裝類
    2022-04-04
  • 在Ubuntu上部署SpringBoot應用的操作步驟

    在Ubuntu上部署SpringBoot應用的操作步驟

    隨著云計算和容器化技術的普及,Linux 服務器已成為部署 Web 應用程序的主流平臺之一,Java 作為一種跨平臺的編程語言,具有廣泛的應用場景,本文將詳細介紹如何在 Ubuntu 服務器上部署 Java 應用,需要的朋友可以參考下
    2025-01-01
  • 詳解基于Spring Cloud幾行配置完成單點登錄開發(fā)

    詳解基于Spring Cloud幾行配置完成單點登錄開發(fā)

    這篇文章主要介紹了詳解基于Spring Cloud幾行配置完成單點登錄開發(fā),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-02-02
  • Java如何Mock FileInputStream問題

    Java如何Mock FileInputStream問題

    這篇文章主要介紹了Java如何Mock FileInputStream問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-09-09
  • SpringBoot詳解實現(xiàn)自定義異常處理頁面方法

    SpringBoot詳解實現(xiàn)自定義異常處理頁面方法

    SpringBoot是Spring全家桶的成員之一,是一種整合Spring技術棧的方式(或者說是框架),同時也是簡化Spring的一種快速開發(fā)的腳手架
    2022-06-06
  • 解決javaWEB中前后臺中文亂碼問題的3種方法

    解決javaWEB中前后臺中文亂碼問題的3種方法

    這篇文章主要介紹了解決javaWEB中前后臺中文亂碼問題的3種方法,中文問題一直是很多人難以解決的問題,對這方面感興趣的朋友可以參考一下
    2015-11-11
  • IDEA將Maven項目中指定文件夾下的xml等文件編譯進classes的方法

    IDEA將Maven項目中指定文件夾下的xml等文件編譯進classes的方法

    這篇文章主要介紹了IDEA將Maven項目中指定文件夾下的xml等文件編譯進classes的方法,幫助大家更好的利用IDEA進行Java的開發(fā)學習,感興趣的朋友可以了解下
    2021-01-01

最新評論

广灵县| 甘谷县| 勃利县| 枝江市| 惠州市| 普兰店市| 长兴县| 敦煌市| 安图县| 云霄县| 汉川市| 高安市| 千阳县| 海城市| 靖宇县| 大庆市| 临海市| 富源县| 万宁市| 横峰县| 托克托县| 常州市| 阿勒泰市| 报价| 通城县| 临沂市| 宁武县| 长岭县| 岳西县| 安西县| 石楼县| 金山区| 兴安盟| 土默特左旗| 大新县| 剑阁县| 平凉市| 万宁市| 钟山县| 辰溪县| 百色市|