详解了
1.$和#的区别
2.数据库连接池。
3.简单了解MySQL企业开发规范
一、Mybatis面试题:$和#的区别是什么?
MyBatis 参数赋值有两种方式,咱们前面使用了 #{} 进行赋值,接下来我们看下二者的区别。
1.1 #是预编译SQL,$是即时SQL
SQL执行流程
1.语法解析,校验SQL有没有问题。
2.SQL优化,编译,制定执行计划。
3.执行SQL
因此
①#是预编译SQL,不是一个完整的SQL。可以进行缓存,参数是不固定的,性能更高。
$时即时SQL,可以直接编译。但不能进行缓存,参数是固定的。性能更低。
②$存在SQL注入的风险
我们分别使用Integer与String类型参数举例
#是预编译SQL,预编译处理。
$是即时SQL,字符直接替换。
预编译SQL的性能更高一点。可以进行缓存
我们编写如下代码。用#和$分别去取值Integer类型参数和String类型参数。
参数为Integer类型时
使用#符号:预编译SQL。(select * from userinfo where id = ?)
@Select("select * from userinfo where id = #{userId}")
UserInfo queryUserInfo(Integer userId);
使用$符号:即时SQL。(select * from userinfo where id = 1)
@Select("select * from userinfo where id = ${userId}")
List<UserInfo> queryUserInfo2(Integer userId);
2.参数为String类型时
使用 # 符号:预编译SQL。(select * from userinfo where username = ?)
# 会拼接引号。
因此 # 适用于需要拼接引号的情况。
而不用拼接引号的情况就是 $ 符号发挥作用的时候了。
@Select("select * from userinfo where username = #{name}")
List<UserInfo> queryUserByName1(@Param("name") String name);
使用$符号:即时SQL。(select * from userinfo where username = admin)(报错)
$不会拼接引号,而是直接拼接上。
@Select("select * from userinfo where username = ${name}")
List<UserInfo> queryUserByName2(@Param("name") String name);
@Test
void queryUserByName2() {
log.info(userInfoMapper.queryUserByName2("admin").toString());
}
使用$符号:即时SQL。(select * from userinfo where username = 'admin')
$不会拼接引号,而是直接拼接上。 因此注意加单引号。或者双引号。
这样就不会报错了
select * from userinfo where username = '${name}'
@Select("select * from userinfo where username = '${name}'")
List<UserInfo> queryUserByName2(@Param("name") String name);
@Test
void queryUserByName2() {
log.info(userInfoMapper.queryUserByName2("admin").toString());
}
注:
如果只有一条结果,那么使用对象接收也可以,使用List<UserInfo>接收也可以。
如果有多条结果,那么只能使用 List 来接收
1.2最主要区别($存在SQL注入的问题。)
1.1.1$存在SQL注入的问题
最主要的区别是:$存在SQL注入的问题。
SQL注入:
是通过操作输入的数据(参数),来修改事先定义好的SQL语句(不完整SQL),以达到执行的代码对服务器进行攻击的方法。
也就是把参数作为SQL的一部分,以达到对服务器进行攻击的目的。
示例:
这个就相当于
select * from userinfo where username = ture
空字符串
' '
的含义:
- 在 SQL 中,空字符串
' '
是一个非空的字符串,因此在布尔上下文中,它会被视为真值(TRUE)。- 因为空字符串不是NULL,所以在逻辑表达式中,它被认为是“有值”的。
1 = '1'
的逻辑:
- SQL 中,字符串
'1'
和整数1
在比较时通常会隐式转换为相同的数据类型。1 = '1'
会被解释为1 = 1
,结果为TRUE
。
执行SQL语句后,我们得到的结果是将表内所有内容都打印出来了。相当于没有where
由于存在SQL注入的问题因此$符号不被广泛使用了。
早期SQL注入的问题:
可以将表删掉,
也可以更新表的数据,因此SQL注入是一个非常严重的问题。
delete的问题不存在了,是因为Mybatis帮我们拦截了,例如我们@select注解中有delete操作,那么就会将之拦截。
通过SQL注入完成无密码就可以登录
也不是说使用$就一定会存在SQL注入问题。而是看你的代码如何去实现的
模拟SQL注入 完成用户登录 代码演示:
1.在Controller包控制器层中(也算是三层架构中的视图层)
@RequestMapping("/login")
public UserInfo login(String userName,String password){
return userService.queryUserByNameAndPassword(userName,password);
}
2.在Service服务层包中
public UserInfo queryUserByNameAndPassword(String name,String password) {
List<UserInfo> userInfos = userInfoMapper.queryUserByNameAndPassword(name,password);
if(!userInfos.isEmpty()){
//如果查出来
return userInfos.get(0);
}
//如果没查出来
return null;
}
3.在Mapper数据访问层中:
4. 访问的数据库表如下
5.最终访问的结果如下:
我们发现:
当我们的密码输入为'or 1='1时数据也可以被访问到,这就是很严重的SQL注入问题。
这就是一个漏洞。
这并不是说如果你使用$符号就一定有问题。如果Mapper层代码我们使用
对象UserInfo来接收的话,那么我们使用' or 1='1也登录不进去,因为这样会报错。是因为UserInfo默认返回一个对象的数据。而如果存在SQL注入,就会返回多个对象的数据。
如果只有一条结果,那么使用对象接收也可以,使用List<UserInfo>接收也可以。
如果有多条结果,那么只能使用 List 来接收。如果使用UserInfo对象来接收就会报错。
还有一些其他地方
如果我们根据userName去查再去通过password校验,这种情况使用' or 1='1也登录不进去
1.3在使用上的其他区别
表名,字段名作为参数时。这些情况需要使用${ }。
1.3.1使用淘宝进行排序问题(不能使用#):
此时需要使用$符号,
注:
此时也存在SQL注入的风险,
解决办法:如果不让用户传参的话,那么就不存在SQL注入风险了
也就是不给用户在用$取值的输入框。
如何来去做呢?请往下看
SQL语句七 由Id进行倒序排序。
select * from userinfo order by id desc
假如我们这样在Mapper包中写数据。那么就会报错。
SQL语句出错,是因为#若参数是字符串会默认加上' '此时
SQL语句变成了
select * from userinfo order by id 'desc'
因此会报错,如果我们改成用$来取值,那么此时就可以编译通过了
注:
此时也存在SQL注入的风险,解决办法:如果不让用户传参的话,那么就不存在SQL注入风险了
也就是不给用户在用$取值的输入框。
如何来去做呢?请往下看
$存在SQL注入风险,如何处理?
实现方式非常多,简单列举以下方式
例如1:不接收用户输入的参数,参数由后台来处理。
代码要写的很严谨,就不存在SQL注入问题了。
升序:url1
降序:url2
不接收用户输入的参数,参数由后台来处理。
对参数进行校验,只接受desc(降序) / asc(升序) 这两个参数
在后端进行校验。如果不是这两个参数则立马报错进行处理。
order参数是Controller包中被用户传进来的
还有哪些情况#不能使用
不需要加引号的时候,比如表名,字段名。
表名,字段名作为变量的时候,
表明作为变量:
在分库分表的情况下,若将userinfo这张表分成userinfo1,userinfo2,...userinfo10这十张表,我们查询的时候,根据一定的规则,看哪张表里有我们想要的userId,此时表名就是变量了。
字段名作为变量:
1.一些大公司不让直接连数据库(不然账号密码直接登录数据库),连了就会有风险,如何规避风险,而是开发一个MySQL客户端,申请权限就可以直接连数据库不需要账号密码。需要解析SQL。字段都是生成的,
2.数据的导出,若只想导出某一些列,此时列名就会被作为变量。#不能使用。
1.3.2模糊查询问题(不能直接使用#,需要通过CONCAT(str1,str2,..)方式再用#实现):
SQL语句八:模糊查询
select * from userinfo where username like "%需要查询的名字%"
例:select * from userinfo where username like "%admin%"
数据库表
我们在Mapper中写出如下代码进行迷糊查询并进行测试:
只查询出来了一条,因为输入结果为admin,我们虽然加了%%但是被弄丢了。因此相当于我们只查了admin没有进行模糊查询。相当于等号。
也就是拼接成了
select * from userinfo where username like admin
相当于
select * from userinfo where username = admin
换一种写法,我们将百分号写在外面
又报错了,SQL语句出错。 拼接成了
select * from userinfo where username like %'admin'%
在里面又加上了引号。
因此模糊查询通过#无法实现。
通过$实现模糊查询
我们发现,成功得到了我们想要的结果。
模糊查询$存在SQL注入风险,如何处理?
使用MySQL的CONCAT(str1,str2,....)
CONCAT()是mysql内置函数
使用示例如下
select * from userinfo where username like CONCAT('%',admin,'%')
改成这种形式 就可以使用#来实现模糊查询
总结
#和$的区别
1.#是预编译SQL,预编译处理。$是即时SQL,字符直接替换。预编译SQL的性能更高一点。可以进行缓存
2.$存在SQL注入的风险。#{ }可以防止SQL注入。
3.查询语句中,可以使用#{ }推荐使用#{ }。#{ }不能完成如排序功能,表名,字段名作为参数时。这些情况需要使用${ }。
4.排序,模糊查询等方式不能直接用#来取值。排序可以进行校验,模糊查询可以通过使用MySQL内置函数进行转化再用#进行查询。
模糊查询虽然${}可以完成,但因为存在SQL注入的问题,所以通常使用mysql内置函数concat来完成
相同点:
#是取值用的
$也是取值用的
二、数据库连接池
数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接, 而不是再重新建立一个。
没有使用数据库连接池的情况:每次执行SQL语句,要先创建一个新的连接对象,然后执行SQL语句,SQL 语句执行完,再关闭连接对象释放资源。这种重复的创建连接,销毁连接比较消耗资源
使用数据库连接池的情况:程序启动时,会在数据库连接池中创建一定数量的Connection对象,当客户请求数据库连接池,会从数据库连接池中获取Connection对象,然后执行SQL,SQL语句执行完,再把 Connection归还给连接池。
使用数据库连接池的好处:
优点:
1. 减少了网络开销
2. 资源重用
3. 提升了系统的性能
常见数据库连接池:C3P0、DBCP、Druid、HiKari。
目前比较流行的数据库连接池是Hikari,Druid
2.1Hikari :
Hikari 是日语"光"的意思(ひかり),Hikari也是以追求性能极致为目标
是SpringBoot默认使用的数据库连接池
Hikaricp和Druid对比_数据库_晚风暖-华为云开发者联盟 (csdn.net)
2.2Druid
如果我们想把默认的数据库连接池切换为Druid数据库连接池,只需要引入相关依赖即可
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.17</version>
</dependency>
Druid连接池是阿里巴巴开源的数据库连接池项目 功能强大,性能优秀是Java语言最好的数据库连接池之⼀。
学习文档:https://github.com/alibaba/druid/wiki/%E9%A6%96%E9%A1%B5
大总结
1.MySQL企业开发规范
重点:
1.表名,字段名用小写字母或数字,单词间用_分割。尽量避免出现数字开头或者两个下划线中间只出现数字。数据库字段名的修改代价很大,所以字段名称需要慎重考虑。
2.表必备三字段:id,create_time,update_time
3.在表查询中, 避免使用*作为查询的字段列表,标明需要哪些字段
2.#{} 和${} 区别
1. #{}:预编译处理, ${}:字符直接替换
2. #{} 可以防⽌SQL注⼊,${}存在SQL注⼊的⻛险,查询语句中,可以使⽤#{},推荐使⽤#{}
3. 但是⼀些场景,#{}不能完成,比如排序功能,表名,字段名作为参数时,这些情况需要使用${}
4. 模糊查询虽然${}可以完成,但因为存在SQL注⼊的问题,所以通常使⽤mysql内置函数concat来完成
3.数据库连接池
目前比较流行的数据库连接池是
Hikari:是SpringBoot默认使用的数据库连接池
Druid:是阿里巴巴开源的数据库连接池项目。功能强大,性能优秀是Java语言最好的数据库连接池之⼀。
优点:
1. 减少了网络开销
2. 资源重用
3. 提升了系统的性能