一、MyBatis中${}和#{}的区别
====================
1.1${}和#{}演示
============
数据库数据:
dao接口:
List findByUsername(String username);
List findByUsername2(String username);
Mapper.xml:
select * from user where username like #{username}
select * from user where username like ‘%${value}%’
执行测试代码:
@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();
}
查看执行结果:
发现都能够查询出来
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语句:
我们传递的参数是aaa’ or 1=1 --,导致查询出来了全部的数据。
大家可以想象一下,如果我是要根据id删除呢?
delete from user where id=‘${value}’
如果我传递的是:1’ or 1=1; --,结果会是什么样,我想大家应该已经知道了。
我这里id是Integer类型,不好测试,就不带大家测试了,大家有兴趣可以自己私下测试。
如果上面使用的是#{}就不会出现SQL注入的问题了
1.3${}和#{}的区别
=============
#{}匹配的是一个占位符,相当于JDBC中的一个?,会对一些敏感的字符进行过滤,编译过后会对传递的值加上双引号,因此可以防止SQL注入问题。
匹配的是真实传递的值,传递过后,会与 s q l 语句进行字符串拼接。 {}匹配的是真实传递的值,传递过后,会与sql语句进行字符串拼接。 匹配的是真实传递的值,传递过后,会与sql语句进行字符串拼接。{}会与其他sql进行字符串拼接,不能预防sql注入问题。
查看#{}和${}生成的SQL语句:
![MyBaits中#{}和
的真正区别,
{}的真正区别,
的真正区别,{}的使用场景,#{}如何防止注入](https://img-blog.csdn
img.cn/img_convert/23c9cc86a3582923d37c30d590d6565f.png)
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()方法来完成,${}则不是):
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;
}
}
}
我们执行#{}的查询语句,打断点观察:
最终传递的参数如下:
最终传递的参数为:‘aaa\’ or 1=1 –
咱们在数据库中执行如下SQL语句(肯定是查询不到数据的):
select * from user where username like ‘aaa’ or 1=1 – ’
如果把PreparedStatement加的那根"/"去掉呢?我们执行SQL试试:
select * from user where username like ‘aaa’ or 1=1 – ’
我们也可以通过MySQL的日志来观察#{}和${}产生的SQL语句来分析问题:
1)开启MySQL日志:
在MySQL配置文件中的[mysqld]下增加如下配置:
是否开启mysql日志 0:关闭(默认值) 1:开启
general-log=1
mysql 日志的存放位置
general_log_file=“D:/query.log”
2)重启MySQL服务(要以管理员身份运行):
net stop mysql
net start mysql
使用mybatis分别执行如下两条SQL语句:
查看MySQL日志:
1.5#{}和${}的应用场景
===============
既然#{}比 好那么多,那为什么还要有 {}好那么多,那为什么还要有 好那么多,那为什么还要有{}这个东西存在呢?干脆都用#{}不就万事大吉吗?
其实不是的, 也有用武之地,我们都知道 {}也有用武之地,我们都知道 也有用武之地,我们都知道{}会产生字符串拼接,来生成一个新的字符串
1.5.1 ${}和#{}用法上的区别
===================
例如现在要进行模糊查询,查询user表中姓张的所有员工的信息
sql语句为:select * from user where name like ‘张%’
此时如果传入的参数是 “张”
如果使用KaTeX parse error: Undefined control sequence: \* at position 11: {}:select \̲*̲ from user wher…{value}%’
生成的sql语句:select * from user where name like ‘张%’
如果使用#{}:select * from user where name like #{value}“%”
生成的sql语句:select * from user where name like ‘张’“%”
如果传入的参数是 “张%”
使用#{}:select * from user where name like #{value}
生成的sql语句:select * from user where name like ‘张%’
使用KaTeX parse error: Undefined control sequence: \* at position 11: {}:select \̲*̲ from user wher…{value}’
生成的sql语句:select * from user where name like ‘张%’
通过上面的SQL语句我们能够发现#{}是会加上双引号,而${}匹配的是真实的值。
还有一点就是如果使用 的话,里面必须要填 v a l u e ,即: {}的话,里面必须要填value,即: 的话,里面必须要填value,即:{value},#{}则随意
1.5.2 什么情况下用${}?
================
场景举例:
代码测试:
执行之后,发现执行成功
我们可以切换一下,把${}改成#{},会出现SQL语法错误的异常
1.6 总结
======
1.6.1 SQL注入问题
=============
MyBatis的#{}之所以能够预防SQL注入是因为底层使用了PreparedStatement类的setString()方法来设置参数,此方法会获取传递进来的参数的每个字符,然后进行循环对比,如果发现有敏感字符(如:单引号、双引号等),则会在前面加上一个’/'代表转义此符号,让其变为一个普通的字符串,不参与SQL语句的生成,达到防止SQL注入的效果。
其次${}本身设计的初衷就是为了参与SQL语句的语法生成,自然而然会导致SQL注入的问题(不会考虑字符过滤问题)。
1.6.2 #{}和KaTeX parse error: Expected 'EOF', got '#' at position 10: {}用法总结 1)#̲{}在使用时,会根据传递进来的…{}则不会,我们需要手动加
2)在传递一个参数时,我们说了#{}中可以写任意的值,
则必须使用
v
a
l
u
e
;即:
{}则必须使用value;即:
则必须使用value;即:{value}
3)#{}针对SQL注入进行了字符过滤,KaTeX parse error: Expected 'EOF', got '#' at position 26: …值,并没有考虑到这些问题 4)#̲{}的应用场景是为给SQL语句…{}的应用场景是为了传递一些需要参与SQL语句语法生成的值。
最后
小编这些年深知大多数初中级工程师,想要提升自己,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此我收集整理了一份《2024年Java全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你需要这些资料,⬅专栏获取
注入是因为底层使用了PreparedStatement类的setString()方法来设置参数,此方法会获取传递进来的参数的每个字符,然后进行循环对比,如果发现有敏感字符(如:单引号、双引号等),则会在前面加上一个’/'代表转义此符号,让其变为一个普通的字符串,不参与SQL语句的生成,达到防止SQL注入的效果。
其次${}本身设计的初衷就是为了参与SQL语句的语法生成,自然而然会导致SQL注入的问题(不会考虑字符过滤问题)。
1.6.2 #{}和KaTeX parse error: Expected 'EOF', got '#' at position 10: {}用法总结 1)#̲{}在使用时,会根据传递进来的…{}则不会,我们需要手动加
2)在传递一个参数时,我们说了#{}中可以写任意的值,
则必须使用
v
a
l
u
e
;即:
{}则必须使用value;即:
则必须使用value;即:{value}
3)#{}针对SQL注入进行了字符过滤,KaTeX parse error: Expected 'EOF', got '#' at position 26: …值,并没有考虑到这些问题 4)#̲{}的应用场景是为给SQL语句…{}的应用场景是为了传递一些需要参与SQL语句语法生成的值。
最后
小编这些年深知大多数初中级工程师,想要提升自己,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此我收集整理了一份《2024年Java全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-zfjUR5Jf-1719519287253)]
[外链图片转存中…(img-v7TLkyLU-1719519287254)]
[外链图片转存中…(img-HJXuTg80-1719519287255)]
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你需要这些资料,⬅专栏获取