成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

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

數(shù)據(jù)庫(kù) 其他數(shù)據(jù)庫(kù)
Mybatis 作為國(guó)內(nèi)開(kāi)發(fā)中常用到的半自動(dòng) orm 框架,相信大家都很熟悉,它提供了簡(jiǎn)單靈活的xml映射配置,方便開(kāi)發(fā)人員編寫(xiě)簡(jiǎn)單、復(fù)雜SQL,在國(guó)內(nèi)互聯(lián)網(wǎng)公司使用眾多。

本文針對(duì)筆者日常開(kāi)發(fā)中對(duì) Mybatis 占位符 #{} 和 ${} 使用時(shí)機(jī)結(jié)合源碼,思考總結(jié)而來(lái)

  • ? Mybatis 版本 3.5.11
  • ? Spring boot 版本 3.0.2
  • ? mybatis-spring 版本 3.0.1
  • ? github地址:https://github.com/wayn111, 歡迎大家關(guān)注,點(diǎn)個(gè)star

一. 啟動(dòng)時(shí),mybatis-spring解析xml文件流程圖

Spring項(xiàng)目啟動(dòng)時(shí),mybatis-spring自動(dòng)初始化解析xml文件核心流程。

流程圖

Mybatis在buildSqlSessionFactory()會(huì)遍歷所有mapperLocations(xml文件)調(diào)用xmlMapperBuilder.parse()解析,源碼如下:

在 parse() 方法中,Mybatis通過(guò)configurationElement(parser.evalNode("/mapper"))方法解析xml文件中的各個(gè)標(biāo)簽。

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文件內(nèi)的namespace、cache-ref、cache、parameterMap、resultMap、sql、select、insert、update、delete等各種標(biāo)簽
        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);
      }
    }
}

最后會(huì)把 namespace、cache-ref、cache、parameterMap、resultMap、select、insert、update、delete等標(biāo)簽內(nèi)容解析結(jié)果放到 builderAssistant 對(duì)象中,將sql標(biāo)簽解析結(jié)果放到sqlFragments對(duì)象中,其中 由于 builderAssistant 對(duì)象會(huì)保存select、insert、update、delete標(biāo)簽內(nèi)容解析結(jié)果我們對(duì) builderAssistant 對(duì)象進(jìn)行深入了解。

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 對(duì)象繼承至 BaseBuilder,BaseBuilder 類(lèi)中包含一個(gè) configuration 對(duì)象屬性, configuration 對(duì)象中會(huì)保存xml文件標(biāo)簽解析結(jié)果至自身對(duì)應(yīng)屬性mappedStatements、caches、resultMaps、sqlFragments。

這里有個(gè)問(wèn)題上面提到的sql標(biāo)簽結(jié)果會(huì)放到 XMLMapperBuilder 類(lèi)的 sqlFragments 對(duì)象中,為什么 Configuration 類(lèi)中也有個(gè) sqlFragments 屬性?

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

原來(lái) XMLMapperBuilder 類(lèi)中的 sqlFragments 屬性就來(lái)自Configuration類(lèi)。

回到主題,在 buildStatementFromContext(context.evalNodes("select|insert|update|delete")) 方法中會(huì)通過(guò)如下調(diào)用。

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

最后通過(guò)parseDynamicTags(context) 方法解析 select、insert、update、delete 標(biāo)簽內(nèi)容將結(jié)果保存在 MixedSqlNode 對(duì)象中的 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 是一個(gè)接口,有10個(gè)實(shí)現(xiàn)類(lèi)如下:

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

二、運(yùn)行中,sql語(yǔ)句占位符#{}和${}的處理

這里直接給出xml文件查詢(xún)方法標(biāo)簽內(nèi)容。

<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 > #{startTime}
        </if>
        <if test="endTime != null and endTime.trim() != ''">
            and create_time < #{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>

運(yùn)行時(shí) Mybatis 動(dòng)態(tài)代理 MapperProxy 對(duì)象的調(diào)用流程,如下:

-> 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 通過(guò) DynamicSqlSource.getBoundSql(Object parameterObject) 方法對(duì) select、insert、update、delete 標(biāo)簽內(nèi)容做 sql 轉(zhuǎn)換處理,代碼如下:

@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;
  }

1、${}占位符處理

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

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

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);
    }

    // 劃重點(diǎn),${}占位符替換邏輯在就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();
    }
}

劃重點(diǎn),${} 占位符處理如下:

handleToken(String content) 方法中, Mybatis 會(huì)通過(guò) ognl 表達(dá)式將 ${} 的結(jié)果直接拼接在 sql 語(yǔ)句中,由此我們得知 ${} 占位符拼接的字段就是我們傳入的原樣字段,有著 Sql 注入風(fēng)險(xiǎn)

2、#{}占位符處理

#{} 占位符文本的 SqlNode 實(shí)現(xiàn)類(lèi)為 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 會(huì)直接將節(jié)點(diǎn)內(nèi)容拼接在 sql 語(yǔ)句中,也就是說(shuō)在 rootSqlNode.apply(context) 方法執(zhí)行完畢后,此時(shí)的 sql 語(yǔ)句如下:

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會(huì)通過(guò)上面提到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());
}

看到了熟悉的 #{ 占位符沒(méi)有,哈哈, Mybatis 對(duì)于 #{} 占位符的處理就在 GenericTokenParser類(lèi)的 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 {
    ... 
    // 劃重點(diǎn),#{}占位符替換邏輯在就SqlSourceBuilder.handleToken(String content)方法中
    @Override
    public String handleToken(String content) {
      parameterMappings.add(buildParameterMapping(content));
      return "?";
    }
}

劃重點(diǎn),#{} 占位符處理如下:

handleToken(String content) 方法中, Mybatis 會(huì)直接將我們的傳入?yún)?shù)轉(zhuǎn)換成問(wèn)號(hào)(就是 jdbc 規(guī)范中的問(wèn)號(hào)),也就是說(shuō)我們的 sql 語(yǔ)句是預(yù)處理的。能夠避免 sql 注入問(wèn)題

三. 總結(jié)

由上經(jīng)過(guò)源碼分析,我們知道 Mybatis 對(duì) #{} 占位符是直接轉(zhuǎn)換成問(wèn)號(hào),拼接預(yù)處理 sql。 ${} 占位符是原樣拼接處理,有sql注入風(fēng)險(xiǎn),最好避免由客戶(hù)端傳入此參數(shù)。

責(zé)任編輯:姜華 來(lái)源: 今日頭條
相關(guān)推薦

2024-12-27 09:32:25

MyBatis代碼

2009-07-22 07:41:00

Scala函數(shù)文本占位符語(yǔ)法

2022-05-25 09:15:01

Swift 5.6占位符

2019-07-17 13:25:03

SparkHadoop架構(gòu)

2022-06-13 09:02:06

Swift類(lèi)型占位符

2024-09-20 06:00:32

2010-09-06 16:27:06

CSSabsoluterelative

2024-06-20 11:52:44

C#占位符代碼

2024-09-06 09:37:45

WebApp類(lèi)加載器Web 應(yīng)用

2025-02-07 00:12:34

C#編程as

2025-04-17 01:00:00

DifyRAGFLow

2025-01-24 08:32:00

運(yùn)算符C#

2017-08-18 12:40:21

Python源碼解析

2017-06-16 20:30:54

Python源碼理解

2015-06-15 10:32:44

Java核心源碼解讀

2024-10-28 08:15:32

2023-11-01 08:08:47

PythonIS運(yùn)算符

2016-08-29 19:12:52

JavascriptBackbone前端

2010-01-27 10:37:17

Android圖片瀏覽

2021-02-20 11:40:35

SpringBoot占位符開(kāi)發(fā)技術(shù)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 成人黄色在线视频 | 欧美性吧 | 久久综合一区 | 天天操天天舔 | 精品国产一区二区三区久久影院 | 精品三级在线观看 | 伊人网综合在线 | 久久久久国产精品一区 | 福利久久 | 成人h动漫亚洲一区二区 | 亚洲一一在线 | 亚洲精品日本 | 国产午夜精品一区二区三区在线观看 | 欧美久久一区 | 91久久精品国产 | 精品麻豆剧传媒av国产九九九 | 午夜一区二区三区视频 | 一区二区三区中文字幕 | 欧美国产免费 | 欧美日韩不卡合集视频 | 欧美国产日韩精品 | 久久久久久久久久久丰满 | 伊人免费观看视频 | 国产在线观看一区二区 | 香蕉一区| 亚洲人人 | 国产精品一区在线播放 | 免费毛片网站 | 视频在线观看一区 | 日本中文字幕视频 | 99热这里都是精品 | 91一区二区三区 | 国产精品日日摸夜夜添夜夜av | 在线国产一区 | 日韩一二区 | 人操人人| 成人h动漫精品一区二区器材 | 成av人电影在线 | av黄色在线观看 | 亚洲婷婷一区 | 国产精品久久久 |