JavaEE进阶----18.<Mybatis补充($和#的区别+数据库连接池)>

详解了

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 = ?)

sql 复制代码
@Select("select * from userinfo where id = #{userId}")
UserInfo queryUserInfo(Integer userId);

使用$符号:即时SQL。(select * from userinfo where id = 1)

sql 复制代码
@Select("select * from userinfo where id = ${userId}")
List<UserInfo> queryUserInfo2(Integer userId);

2.参数为String类型时

使用 # 符号:预编译SQL。(select * from userinfo where username = ?)

会拼接引号。

因此 # 适用于需要拼接引号的情况。

而不用拼接引号的情况就是 $ 符号发挥作用的时候了。

sql 复制代码
    @Select("select * from userinfo where username = #{name}")
    List<UserInfo> queryUserByName1(@Param("name") String name);

使用$符号:即时SQL。(select * from userinfo where username = admin)(报错)

$不会拼接引号,而是直接拼接上。

java 复制代码
    @Select("select * from userinfo where username = ${name}")
    List<UserInfo> queryUserByName2(@Param("name") String name);
java 复制代码
    @Test
    void queryUserByName2() {
        log.info(userInfoMapper.queryUserByName2("admin").toString());
    }

使用$符号:即时SQL。(select * from userinfo where username = 'admin')

$不会拼接引号,而是直接拼接上。 因此注意加单引号。或者双引号。

这样就不会报错了

select * from userinfo where username = '${name}'

sql 复制代码
    @Select("select * from userinfo where username = '${name}'")
    List<UserInfo> queryUserByName2(@Param("name") String name);
java 复制代码
    @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包控制器层中(也算是三层架构中的视图层)
java 复制代码
​​​​​​​​​    @RequestMapping("/login")
    public UserInfo login(String userName,String password){
        return userService.queryUserByNameAndPassword(userName,password);
    }
2.在Service服务层包中
java 复制代码
    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. 提升了系统的性能

相关推荐
YDS8299 分钟前
苍穹外卖 —— Spring Cache和购物车功能开发
java·spring boot·后端·spring·mybatis
苍老流年9 分钟前
1. SpringBoot初始化器ApplicationContextInitializer使用与源码分析
java·spring boot·后端
劲墨难解苍生苦9 分钟前
spring ai alibaba mcp 开发demo
java·人工智能
leonardee9 分钟前
Spring 中的 @ExceptionHandler 注解详解与应用
java·后端
不爱编程的小九九10 分钟前
小九源码-springboot103-踏雪阁民宿订购平台
java·开发语言·spring boot
Elieal11 分钟前
Spring 框架核心技术全解析
java·spring·sqlserver
组合缺一12 分钟前
(对标 Spring)OpenSolon v3.7.0, v3.6.4, v3.5.8, v3.4.8 发布(支持 LTS)
java·后端·spring·web·solon
wheeldown28 分钟前
【Linux】Linux 地址空间 + 页表映射的概念解析
java·linux·jvm
源码宝32 分钟前
一套随访系统源码,医院随访管理系统源码,三级随访平台源码,技术框架:Java+Spring boot,Vue,Ant-Design+MySQL5
java·源码·软件开发·程序·随访·随访系统源码·三级随访
♡喜欢做梦34 分钟前
Spring IOC
java·后端·spring