MyBaits中#{}和${}的真正区别,${}的使用场景

执行测试代码:

@Test

public void findByUsername() throws Exception {

InputStream in = Resources.getResourceAsStream(“SqlMapConfig.xml”);

SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();

SqlSessionFactory factory = builder.build(in);

// true:自动提交

SqlSession session = factory.openSession(true);

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

List userList = userDao.findByUsername(“%小%”);

List 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();

}

查看执行结果:

MyBaits中#{}和{}的使用场景,#{}如何防止注入

发现都能够查询出来

1.2 SQL注入问题

===========

${}会产生SQL注入,#{}不会产生SQL注入问题

我们做一个测试:

List userList2 = userDao.findByUsername2(" aaa’ or 1=1 – ");

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

for (User user : userList2) {

System.out.println(user);

}

查询生成的SQL语句:

MyBaits中#{}和{}的使用场景,#{}如何防止注入

我们传递的参数是aaa’ or 1=1 --,导致查询出来了全部的数据。

大家可以想象一下,如果我是要根据id删除呢?

delete from user where id=‘${value}’

如果我传递的是:1’ or 1=1; --,结果会是什么样,我想大家应该已经知道了。

我这里id是Integer类型,不好测试,就不带大家测试了,大家有兴趣可以自己私下测试。

如果上面使用的是#{}就不会出现SQL注入的问题了

MyBaits中#{}和{}的使用场景,#{}如何防止注入

1.3${}和#{}的区别

=============

#{}匹配的是一个占位符,相当于JDBC中的一个?,会对一些敏感的字符进行过滤,编译过后会对传递的值加上双引号,因此可以防止SQL注入问题。

匹配的是真实传递的值,传递过后,会与 s q l 语句进行字符串拼接。 {}匹配的是真实传递的值,传递过后,会与sql语句进行字符串拼接。 匹配的是真实传递的值,传递过后,会与sql语句进行字符串拼接。{}会与其他sql进行字符串拼接,不能预防sql注入问题。

查看#{}和${}生成的SQL语句:

MyBaits中#{}和{}的使用场景,#{}如何防止注入

String abc=“123”;

#{abc}=“123”

${value}=123;

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

====================

1.4.1 网上的答案

===========

网上关于这类问题非常多,总结出来就两个原因:

1)#{}底层采用的是PreparedStatement,会预编译,因此不会产生SQL注入问题;

其实预编译是MySQL自己本身的功能,和PreparedStatement没关系;而且预编译也不是咱们理解的那个预编译,再者PreparedStatement底层默认根本没有用到预编译(要我们手动开启)!详细往下看

2)#{}不会产生字符串拼接, 会产生字符串拼接,因此 {}会产生字符串拼接,因此 会产生字符串拼接,因此{}会出现SQL注入问题;

这两个答案都经不起深究,最终答案也只是停留在表面,也没人知道具体是为什么。

1.4.2 为什么能防止SQL注入?

==================

我们翻开MySQL驱动的源码一看究竟;

打开PreparedStatement类的setString()方法(MyBatis在#{}传递参数时,是借助setString()方法来完成,${}则不是):

MyBaits中#{}和{}的使用场景,#{}如何防止注入

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) { //遍历字符串,获取到每个字符

char c = x.charAt(i);

switch© {

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

cbuf.position(0);

this.charsetEncoder.encode(cbuf, bbuf, true);

if (bbuf.get(0) == 92) {

buf.append(‘\’);

}

}

buf.append©;

break;

default:

buf.append©;

}

}

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;

}

}

}

我们执行#{}的查询语句,打断点观察:

MyBaits中#{}和{}的使用场景,#{}如何防止注入

最终传递的参数如下:

MyBaits中#{}和{}的使用场景,#{}如何防止注入

最终传递的参数为:‘aaa\’ or 1=1 –

咱们在数据库中执行如下SQL语句(肯定是查询不到数据的):

select * from user where username like ‘aaa’ or 1=1 – ’

MyBaits中#{}和{}的使用场景,#{}如何防止注入

如果把PreparedStatement加的那根"/"去掉呢?我们执行SQL试试:

select * from user where username like ‘aaa’ or 1=1 – ’

MyBaits中#{}和{}的使用场景,#{}如何防止注入

我们也可以通过MySQL的日志来观察#{}和${}产生的SQL语句来分析问题:

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

这份《“java高分面试指南”-25分类227页1000+题50w+字解析》同样可分享给有需要的朋友,感兴趣的伙伴们可挑战一下自我,在不看答案解析的情况,测试测试自己的解题水平,这样也能达到事半功倍的效果!(好东西要大家一起看才香)

image

image

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
%以上Java开发知识点,真正体系化!**

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

这份《“java高分面试指南”-25分类227页1000+题50w+字解析》同样可分享给有需要的朋友,感兴趣的伙伴们可挑战一下自我,在不看答案解析的情况,测试测试自己的解题水平,这样也能达到事半功倍的效果!(好东西要大家一起看才香)

[外链图片转存中…(img-kbdIG7gI-1713475337240)]

[外链图片转存中…(img-1LFolLQI-1713475337240)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值