欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

mybatis中#{}和${}的區(qū)別詳解

 更新時(shí)間:2022年01月24日 15:31:15   作者:緑水長(zhǎng)流*z  
本文主要介紹了mybatis中#{}和${}的區(qū)別詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下

一、MyBatis中${}和#{}的區(qū)別

1.1 ${}和#{}演示

數(shù)據(jù)庫(kù)數(shù)據(jù):

在這里插入圖片描述

dao接口:

List<User> findByUsername(String username);

List<User> findByUsername2(String username);

Mapper.xml:

<!-- 使用#{} -->
<select id="findByUsername" parameterType="java.lang.String" resultType="com.lscl.entity.User">
    select * from user where username like #{username}
</select>

<!-- 使用${},注意${}中的值必須要填value -->
<select id="findByUsername2" parameterType="java.lang.String" resultType="com.lscl.entity.User">
    select * from user where username like '%${value}%'
</select>

執(zhí)行測(cè)試代碼:

@Test
public void findByUsername() throws Exception {
    InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");

    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();

    SqlSessionFactory factory = builder.build(in);

    // true:自動(dòng)提交
    SqlSession session = factory.openSession(true);

    UserDao userDao = session.getMapper(UserDao.class);

    List<User> userList = userDao.findByUsername("%小%");
    List<User> userList2 = userDao.findByUsername2("小");

    System.out.println("userList: ");
    for (User user : userList) {
        System.out.println(user);
    }

    System.out.println("userList2: ");

    for (User user : userList2) {
        System.out.println(user);
    }

    session.close();
    in.close();
}

查看執(zhí)行結(jié)果:

在這里插入圖片描述

發(fā)現(xiàn)都能夠查詢出來(lái)

1.2 SQL注入問(wèn)題

${}會(huì)產(chǎn)生SQL注入,#{}不會(huì)產(chǎn)生SQL注入問(wèn)題

我們做一個(gè)測(cè)試:

List<User> userList2 = userDao.findByUsername2(" aaa' or 1=1 -- ");

System.out.println("userList2: ");

for (User user : userList2) {
    System.out.println(user);
}

查詢生成的SQL語(yǔ)句:

在這里插入圖片描述

我們傳遞的參數(shù)是aaa' or 1=1 --,導(dǎo)致查詢出來(lái)了全部的數(shù)據(jù)。

大家可以想象一下,如果我是要根據(jù)id刪除呢?

delete from user where id='${value}'

如果我傳遞的是:1' or 1=1; --,結(jié)果會(huì)是什么樣,我想大家應(yīng)該已經(jīng)知道了。

我這里id是Integer類型,不好測(cè)試,就不帶大家測(cè)試了,大家有興趣可以自己私下測(cè)試。

如果上面使用的是#{}就不會(huì)出現(xiàn)SQL注入的問(wèn)題了

在這里插入圖片描述

1.3 ${}和#{}的區(qū)別

#{}匹配的是一個(gè)占位符,相當(dāng)于JDBC中的一個(gè)?,會(huì)對(duì)一些敏感的字符進(jìn)行過(guò)濾,編譯過(guò)后會(huì)對(duì)傳遞的值加上雙引號(hào),因此可以防止SQL注入問(wèn)題。

${}匹配的是真實(shí)傳遞的值,傳遞過(guò)后,會(huì)與sql語(yǔ)句進(jìn)行字符串拼接。${}會(huì)與其他sql進(jìn)行字符串拼接,不能預(yù)防sql注入問(wèn)題。

查看#{}${}生成的SQL語(yǔ)句:

在這里插入圖片描述

String abc=“123”;

#{abc}="123"

${value}=123;

1.4 #{}底層是如何防止SQL注入的?

1.4.1 網(wǎng)上的答案

網(wǎng)上關(guān)于這類問(wèn)題非常多,總結(jié)出來(lái)就兩個(gè)原因:

1)#{}底層采用的是PreparedStatement,會(huì)預(yù)編譯,因此不會(huì)產(chǎn)生SQL注入問(wèn)題;

其實(shí)預(yù)編譯是MySQL自己本身的功能,和PreparedStatement沒(méi)關(guān)系;而且預(yù)編譯也不是咱們理解的那個(gè)預(yù)編譯,再者PreparedStatement底層默認(rèn)根本沒(méi)有用到預(yù)編譯(要我們手動(dòng)開(kāi)啟)!詳細(xì)往下看

2)#{}不會(huì)產(chǎn)生字符串拼接,${}會(huì)產(chǎn)生字符串拼接,因此${}會(huì)出現(xiàn)SQL注入問(wèn)題;

這兩個(gè)答案都經(jīng)不起深究,最終答案也只是停留在表面,也沒(méi)人知道具體是為什么。

1.4.2 為什么能防止SQL注入?

我們翻開(kāi)MySQL驅(qū)動(dòng)的源碼一看究竟;

打開(kāi)PreparedStatement類的setString()方法(MyBatis在#{}傳遞參數(shù)時(shí),是借助setString()方法來(lái)完成,${}則不是):

在這里插入圖片描述

setString()方法全部源碼:

public void setString(int parameterIndex, String x) throws SQLException {
        synchronized(this.checkClosed().getConnectionMutex()) {
            if (x == null) {
                this.setNull(parameterIndex, 1);
            } else {
                this.checkClosed();
                int stringLength = x.length();
                StringBuilder buf;
                if (this.connection.isNoBackslashEscapesSet()) {
                    boolean needsHexEscape = this.isEscapeNeededForString(x, stringLength);
                    Object parameterAsBytes;
                    byte[] parameterAsBytes;
                    if (!needsHexEscape) {
                        parameterAsBytes = null;
                        buf = new StringBuilder(x.length() + 2);
                        buf.append('\'');
                        buf.append(x);
                        buf.append('\'');
                        if (!this.isLoadDataQuery) {
                            parameterAsBytes = StringUtils.getBytes(buf.toString(), this.charConverter, this.charEncoding, this.connection.getServerCharset(), this.connection.parserKnowsUnicode(), this.getExceptionInterceptor());
                        } else {
                            parameterAsBytes = StringUtils.getBytes(buf.toString());
                        }

                        this.setInternal(parameterIndex, parameterAsBytes);
                    } else {
                        parameterAsBytes = null;
                        if (!this.isLoadDataQuery) {
                            parameterAsBytes = StringUtils.getBytes(x, this.charConverter, this.charEncoding, this.connection.getServerCharset(), this.connection.parserKnowsUnicode(), this.getExceptionInterceptor());
                        } else {
                            parameterAsBytes = StringUtils.getBytes(x);
                        }

                        this.setBytes(parameterIndex, parameterAsBytes);
                    }

                    return;
                }

                String parameterAsString = x;
                boolean needsQuoted = true;
                if (this.isLoadDataQuery || this.isEscapeNeededForString(x, stringLength)) {
                    needsQuoted = false;
                    buf = new StringBuilder((int)((double)x.length() * 1.1D));
                    buf.append('\'');

                    for(int i = 0; i < stringLength; ++i) {		//遍歷字符串,獲取到每個(gè)字符
                        char c = x.charAt(i);
                        switch(c) {
                        case '\u0000':
                            buf.append('\\');
                            buf.append('0');
                            break;
                        case '\n':
                            buf.append('\\');
                            buf.append('n');
                            break;
                        case '\r':
                            buf.append('\\');
                            buf.append('r');
                            break;
                        case '\u001a':
                            buf.append('\\');
                            buf.append('Z');
                            break;
                        case '"':
                            if (this.usingAnsiMode) {
                                buf.append('\\');
                            }

                            buf.append('"');
                            break;
                        case '\'':
                            buf.append('\\');
                            buf.append('\'');
                            break;
                        case '\\':
                            buf.append('\\');
                            buf.append('\\');
                            break;
                        case '¥':
                        case '?':
                            if (this.charsetEncoder != null) {
                                CharBuffer cbuf = CharBuffer.allocate(1);
                                ByteBuffer bbuf = ByteBuffer.allocate(1);
                                cbuf.put(c);
                                cbuf.position(0);
                                this.charsetEncoder.encode(cbuf, bbuf, true);
                                if (bbuf.get(0) == 92) {
                                    buf.append('\\');
                                }
                            }

                            buf.append(c);
                            break;
                        default:
                            buf.append(c);
                        }
                    }

                    buf.append('\'');
                    parameterAsString = buf.toString();
                }

                buf = null;
                byte[] parameterAsBytes;
                if (!this.isLoadDataQuery) {
                    if (needsQuoted) {
                        parameterAsBytes = StringUtils.getBytesWrapped(parameterAsString, '\'', '\'', this.charConverter, this.charEncoding, this.connection.getServerCharset(), this.connection.parserKnowsUnicode(), this.getExceptionInterceptor());
                    } else {
                        parameterAsBytes = StringUtils.getBytes(parameterAsString, this.charConverter, this.charEncoding, this.connection.getServerCharset(), this.connection.parserKnowsUnicode(), this.getExceptionInterceptor());
                    }
                } else {
                    parameterAsBytes = StringUtils.getBytes(parameterAsString);
                }

                this.setInternal(parameterIndex, parameterAsBytes);
                this.parameterTypes[parameterIndex - 1 + this.getParameterIndexOffset()] = 12;
            }

        }
    }

我們執(zhí)行#{}的查詢語(yǔ)句,打斷點(diǎn)觀察:

在這里插入圖片描述

最終傳遞的參數(shù)如下:

在這里插入圖片描述

最終傳遞的參數(shù)為:'aaa\' or 1=1 --

咱們?cè)跀?shù)據(jù)庫(kù)中執(zhí)行如下SQL語(yǔ)句(肯定是查詢不到數(shù)據(jù)的):

select * from user where username like 'aaa\' or 1=1 -- '

在這里插入圖片描述

如果把PreparedStatement加的那根"/"去掉呢?我們執(zhí)行SQL試試:

select * from user where username like 'aaa' or 1=1 -- '

在這里插入圖片描述

我們也可以通過(guò)MySQL的日志來(lái)觀察#{}和${}產(chǎn)生的SQL語(yǔ)句來(lái)分析問(wèn)題:

1)開(kāi)啟MySQL日志:

在MySQL配置文件中的[mysqld]下增加如下配置:

# 是否開(kāi)啟mysql日志  0:關(guān)閉(默認(rèn)值) 1:開(kāi)啟
general-log=1

# mysql 日志的存放位置
general_log_file="D:/query.log"

在這里插入圖片描述

2)重啟MySQL服務(wù)(要以管理員身份運(yùn)行):

在這里插入圖片描述

net stop mysqlnet start mysql

使用mybatis分別執(zhí)行如下兩條SQL語(yǔ)句:

在這里插入圖片描述

查看MySQL日志:

在這里插入圖片描述

1.5 #{}和${}的應(yīng)用場(chǎng)景

既然#{}${}好那么多,那為什么還要有${}這個(gè)東西存在呢?干脆都用#{}不就萬(wàn)事大吉嗎?

其實(shí)不是的,${}也有用武之地,我們都知道${}會(huì)產(chǎn)生字符串拼接,來(lái)生成一個(gè)新的字符串

1.5.1 ${}和#{}用法上的區(qū)別

例如現(xiàn)在要進(jìn)行模糊查詢,查詢user表中姓張的所有員工的信息

sql語(yǔ)句為:select * from user where name like '張%'

此時(shí)如果傳入的參數(shù)是 “張”

如果使用${}select * from user where name like '${value}%'

生成的sql語(yǔ)句:select * from user where name like '張%'

如果使用#{}select * from user where name like #{value}"%"

生成的sql語(yǔ)句:select * from user where name like '張'"%"

如果傳入的參數(shù)是 “張%”

使用#{}:select * from user where name like #{value}

生成的sql語(yǔ)句:select * from user where name like '張%'

使用${}select * from user where name like '${value}'

生成的sql語(yǔ)句:select * from user where name like '張%'

通過(guò)上面的SQL語(yǔ)句我們能夠發(fā)現(xiàn)#{}是會(huì)加上雙引號(hào),而${}匹配的是真實(shí)的值。

還有一點(diǎn)就是如果使用${}的話,里面必須要填value,即:${value},#{}則隨意

1.5.2 什么情況下用${}?

場(chǎng)景舉例:

在這里插入圖片描述

代碼測(cè)試:

在這里插入圖片描述

執(zhí)行之后,發(fā)現(xiàn)執(zhí)行成功

在這里插入圖片描述

我們可以切換一下,把${}改成#{},會(huì)出現(xiàn)SQL語(yǔ)法錯(cuò)誤的異常

在這里插入圖片描述

1.6 總結(jié)

1.6.1 SQL注入問(wèn)題

MyBatis的#{}之所以能夠預(yù)防SQL注入是因?yàn)榈讓邮褂昧?code>PreparedStatement類的setString()方法來(lái)設(shè)置參數(shù),此方法會(huì)獲取傳遞進(jìn)來(lái)的參數(shù)的每個(gè)字符,然后進(jìn)行循環(huán)對(duì)比,如果發(fā)現(xiàn)有敏感字符(如:?jiǎn)我?hào)、雙引號(hào)等),則會(huì)在前面加上一個(gè)'/'代表轉(zhuǎn)義此符號(hào),讓其變?yōu)橐粋€(gè)普通的字符串,不參與SQL語(yǔ)句的生成,達(dá)到防止SQL注入的效果。

其次${}本身設(shè)計(jì)的初衷就是為了參與SQL語(yǔ)句的語(yǔ)法生成,自然而然會(huì)導(dǎo)致SQL注入的問(wèn)題(不會(huì)考慮字符過(guò)濾問(wèn)題)。

1.6.2 #{}和${}用法總結(jié)

1)#{}在使用時(shí),會(huì)根據(jù)傳遞進(jìn)來(lái)的值來(lái)選擇是否加上雙引號(hào),因此我們傳遞參數(shù)的時(shí)候一般都是直接傳遞,不用加雙引號(hào),${}則不會(huì),我們需要手動(dòng)加

2)在傳遞一個(gè)參數(shù)時(shí),我們說(shuō)了#{}中可以寫任意的值,${}則必須使用value;即:${value}

3)#{}針對(duì)SQL注入進(jìn)行了字符過(guò)濾,${}則只是作為普通傳值,并沒(méi)有考慮到這些問(wèn)題

4)#{}的應(yīng)用場(chǎng)景是為給SQL語(yǔ)句的where字句傳遞條件值,${}的應(yīng)用場(chǎng)景是為了傳遞一些需要參與SQL語(yǔ)句語(yǔ)法生成的值。

到此這篇關(guān)于MyBaits中#{}和${}的區(qū)別詳解的文章就介紹到這了,更多相關(guān)MyBaits #{}和${}內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java設(shè)計(jì)模式之java策略模式詳解

    Java設(shè)計(jì)模式之java策略模式詳解

    這篇文章主要介紹了Java經(jīng)典設(shè)計(jì)模式之策略模式,簡(jiǎn)單說(shuō)明了策略模式的概念、原理并結(jié)合實(shí)例形式分析了java策略模式的具有用法與相關(guān)注意事項(xiàng),需要的朋友可以參考下
    2021-09-09
  • springmvc處理模型數(shù)據(jù)ModelAndView過(guò)程詳解

    springmvc處理模型數(shù)據(jù)ModelAndView過(guò)程詳解

    這篇文章主要介紹了springmvc處理模型數(shù)據(jù)ModelAndView過(guò)程詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-01-01
  • Java中利用BitMap位圖實(shí)現(xiàn)海量級(jí)數(shù)據(jù)去重

    Java中利用BitMap位圖實(shí)現(xiàn)海量級(jí)數(shù)據(jù)去重

    有許多方法可以用來(lái)去重,比如使用列表、集合等等,但這些方法通常只適用于一般情況,然而,當(dāng)涉及到大量數(shù)據(jù)去重時(shí),常見(jiàn)的 Java Set、List,甚至是 Java 8 的新特性 Stream 流等方式就顯得不太合適了,本文給大家介紹了Java中利用BitMap位圖實(shí)現(xiàn)海量級(jí)數(shù)據(jù)去重
    2024-04-04
  • Java擦除和轉(zhuǎn)換實(shí)例分析

    Java擦除和轉(zhuǎn)換實(shí)例分析

    這篇文章主要介紹了Java擦除和轉(zhuǎn)換,結(jié)合實(shí)例形式分析了java擦除和轉(zhuǎn)換概念、功能及相關(guān)操作技巧,需要的朋友可以參考下
    2019-07-07
  • JSP 開(kāi)發(fā)之hibernate的hql查詢多對(duì)多查詢

    JSP 開(kāi)發(fā)之hibernate的hql查詢多對(duì)多查詢

    這篇文章主要介紹了JSP 開(kāi)發(fā)之hibernate的hql查詢多對(duì)多查詢的相關(guān)資料,希望通過(guò)本文能幫助到大家,需要的朋友可以參考下
    2017-09-09
  • Java操作數(shù)據(jù)庫(kù)連接池案例講解

    Java操作數(shù)據(jù)庫(kù)連接池案例講解

    這篇文章主要介紹了Java操作數(shù)據(jù)庫(kù)連接池案例講解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-08-08
  • 詳解Java線程池和Executor原理的分析

    詳解Java線程池和Executor原理的分析

    這篇文章主要介紹了詳解Java線程池和Executor原理的分析的相關(guān)資料,這里提供實(shí)例及分析原理幫助大家理解這部分知識(shí),需要的朋友可以參考下
    2017-07-07
  • Linux(centos7)安裝jdk1.8的詳細(xì)步驟

    Linux(centos7)安裝jdk1.8的詳細(xì)步驟

    Linux的使用相信大家都要用到j(luò)ava吧,在使用java前我們得先安裝jdk以及配置環(huán)境變量等工作,下面這篇文章主要給大家介紹了關(guān)于Linux(centos7)安裝jdk1.8的詳細(xì)步驟,需要的朋友可以參考下
    2023-10-10
  • java數(shù)據(jù)結(jié)構(gòu)與算法之桶排序?qū)崿F(xiàn)方法詳解

    java數(shù)據(jù)結(jié)構(gòu)與算法之桶排序?qū)崿F(xiàn)方法詳解

    這篇文章主要介紹了java數(shù)據(jù)結(jié)構(gòu)與算法之桶排序?qū)崿F(xiàn)方法,結(jié)合具體實(shí)例形式詳細(xì)分析了桶排序的概念、原理、實(shí)現(xiàn)方法與相關(guān)操作技巧,需要的朋友可以參考下
    2017-05-05
  • Java 8 Stream 的終極技巧——Collectors 功能與操作方法詳解

    Java 8 Stream 的終極技巧——Collectors 功能與操作方法詳解

    這篇文章主要介紹了Java 8 Stream Collectors 功能與操作方法,結(jié)合實(shí)例形式詳細(xì)分析了Java 8 Stream Collectors 功能、操作方法及相關(guān)注意事項(xiàng),需要的朋友可以參考下
    2020-05-05

最新評(píng)論