当前位置: 首页 > news >正文

【MyBatis】 吃透 MyBatis:多表查询、SQL 注入防护(#{} vs ${})与连接池优化

文章目录

  • 一.MyBatis 的其他查询操作
    • (一) 多表查询(企业中尽量避免使用)
      • 1.准备工作
      • 2.数据查询
    • (二) #{} 和 ${}
      • 1.#{}和${} 的使用
      • 2.#{ }和${ }的区别
    • (三) 排序功能
    • (四) like 查询
      • 内置函数 concat()
  • 二.数据库连接池
    • (一) 介绍
    • (二) 使用
      • 1. Hikari : SpringBoot默认使用的数据库连接池
      • 2. Druid
  • 三.总结
    • (一) MySQL 开发企业规范
    • (二) #{}和${}区别

一.MyBatis 的其他查询操作

(一) 多表查询(企业中尽量避免使用)

1.准备工作

我们再建一张文章表,与前面的用户表,进行多表关联查询;文章表的uid,对应用户表的id

  • 数据准备
-- 创建文章表
DROP TABLE IF EXISTS articleinfo;CREATE TABLE articleinfo (id INT PRIMARY KEY auto_increment,title VARCHAR ( 100 ) NOT NULL,content TEXT NOT NULL,uid INT NOT NULL,delete_flag TINYINT ( 4 ) DEFAULT 0 COMMENT '0-正常,1-删除',create_time DATETIME DEFAULT now(),update_time DATETIME DEFAULT now()
) DEFAULT charset 'utf8mb4';-- 插入测试数据
INSERT INTO articleinfo ( title, content, uid ) VALUES ( 'Java', 'Java正文', 1 );
  • 对应的Model

在这里插入图片描述

@Data
public class ArticleInfo {private Integer id;private String title;private String content;private Integer uid;private Integer deleteFlag;private Date createTime;private Date updateTime;private String username;private Integer age;
}

2.数据查询

需求:根据uid查询作者的名称等相关信息
SQL:

1 SELECT
2     ta.id,
3     ta.title,
4     ta.content,
5     ta.uid,
6     tb.username,
7     tb.age,
8     tb.gender
9 FROM
10     articleinfo ta
11     LEFT JOIN user_info tb ON ta.uid = tb.id
12 WHERE
13     ta.id =1
  • 接口定义
@Mapper
public interface ArticleInfoMapper {//boolean selectArticleInfoById(int i);@Select("SELECT" +"    ta.id," +"    ta.title," +"    ta.content," +"    ta.uid," +"    tb.username," +"    tb.age," +"    tb.gender" +"FROM" +"     articleinfo ta" +"     LEFT JOIN user_info tb ON ta.uid = tb.id" +" WHERE" +"     ta.id =1")ArticleInfo selectArticleInfoById(Integer id);
}

如果名称不一致的,采用ResultMap,或者别名的方式解决,和单表查询一样
Mybatis 不分单表还是多表,主要就是三部分: SQL,映射关系和实体类

通过映射关系,把SQL运行结果和实体类关联起来.

(二) #{} 和 ${}

MyBatis 参数赋值有两种方式,咱们前面使用了 #{} 进行赋值,接下来我们看下二者的区别

1.#{}和${} 的使用

  • 1. 先看Interger类型的参数
 @Select("select username, `password`, age, gender, phone from user_info where id= #{id} ")UserInfo queryById(Integer id);

在这里插入图片描述
发现我们输出的SQL语句:

 select username, `password`, age, gender, phone from user_info where id= ?

我们输入的参数并没有在后面拼接,id的值是使用 ? 进行占位. 这种SQL 我们称之为"预编译SQL"
MySQL 中的 JDBC编程使用的就是预编译SQL
我们把 #{} 改成 ${} 再观察打印的日志:

 @Select("select username, `password`, age, gender, phone from user_info where id= ${id} ")UserInfo queryById(Integer id);

在这里插入图片描述
可以看到,这次的参数是直接拼接在SQL语句中了.

  • 2. 接下来我们再看String类型的参数
 @Select("select username, `password`, age, gender, phone from user_info where username= #{name} ")UserInfo queryByName(String name);

在这里插入图片描述
结果是正常返回的

我们把 #{} 改成 ${} 再观察打印的日志:

在这里插入图片描述

可以看到,这次的参数依然是直接拼接在SQL语句中了,但是字符串作为参数时,需要添加引号 '',使用 ${} 不会拼接引号 '',导致程序报错.
修改代码如下:

 @Select("select username, `password`, age, gender, phone from user_info where username= '${name}' ")UserInfo queryByName(String name);

再次运行,结果正常返回

从上面关于Interger类型的参数和String类型的参数的例子可以看出:

#{} 使用的是预编译SQL,通过 ? 占位的方式,提前对SQL进行编译,然后把参数填充到SQL语句中. #{} 会根据参数类型,自动拼接引号 ''.

${} 会直接进行字符替换,一起对SQL进行编译.如果参数为字符串,需要加上引号 ''.
参数为数字类型时,也可以加上,查询结果不变,但是可能会导致索引失效,性能下降.

2.#{ }和${ }的区别

#{ }和${ }的区别就是
1.SQL注入
2.预编译SQL即时SQL的区别.

当客户发送一条SQL语句给服务器后,大致流程如下:

  1. 解析语法和语义,校验SQL语句是否正确
  2. 优化SQL语句,制定执行计划
  3. 执行并返回结果
    一条 SQL如果走上述流程处理,我们称之为 Immediate Statements(即时 SQL)
  • 1. 性能更高
    绝大多数情况下,某一条 SQL 语句可能会被反复调用执行,或者每次执行的时候只有个别的值不同(比如 select 的 where 子句值不同, update 的 set 子句值不同, insert 的 values 值不同). 如果每次都需要经过上面的语法解析, SQL优化、SQL编译等,则效率就明显不行了.

在这里插入图片描述
预编译SQL,编译一次之后会将编译后的SQL语句缓存起来,后面再次执行这条语句时,不会再次编译(只是输入的参数不同),省去了解析优化等过程,以此来提高效率

  • 2. 更安全(防止SQL注入)
    SQL注入:是通过操作输入的数据来修改事先定义好的SQL语句,以达到执行代码对服务器进行攻击的方法。
    由于没有对用户输入进行充分检查,而SQL又是拼接而成,在用户输入参数时,在参数中添加一些SQL关键字,达到改变SQL运行结果的目的,也可以完成恶意攻击。
    sql注入代码:' or 1='1

先来看看SQL注入的例子

 @Select("select username, `password`, age, gender, phone from user_info where username= '${name}' ")List<UserInfo> queryByName(String name);

正常访问情况:

1 @Test
2 void queryByName() {
3     List<UserInfo> userInfos = userInfoMapper.queryByName("admin");
4     System.out.println(userInfos);
5 }

结果运行正常

在这里插入图片描述

  • SQL注入场景:
1 @Test
2 void queryByName() {
3     List<UserInfo> userInfos = userInfoMapper.queryByName("' or 1='1");
4     System.out.println(userInfos);
5 }

结果依然被正确查询出来了,其中参数or被当做了SQL语句的一部分

在这里插入图片描述
可以看出来,查询的数据并不是自己想要的数据.所以用于查询的字段,尽量使用 #{} 预查询的方式
SQL注入是一种非常常见的数据库攻击手段,SQL注入漏洞也是网络世界中最普遍的漏洞之一.
如果发生在用户登录的场景中,密码输入为 ' or 1='1 ,就可能完成登录(不是一定会发生的场景,需要看登录代码如何写)

(三) 排序功能

从上面的例子中,可以得出结论:${}会有SQL注入的风险,所以我们尽量使用#{}完成查询
既然如此,是不是 ${} 就没有存在的必要性了呢?
当然不是.

接下来我们看下${}的使用场景
在这里插入图片描述

(四) like 查询

like 使用 #{} 报错

1 @Select("select id, username, age, gender, phone, delete_flag, create_time, update_time " +
2         "from user_info where username like '%#{key}%' ")
3 List<UserInfo> queryAllUserByLike(String key);

#{} 改成 ${} 可以正确查出来,但是${}存在SQL注入的问题,所以不能直接使用 ${}.

内置函数 concat()

解决办法: 使用 mysql 的内置函数 concat() 来处理,实现代码如下:

1 @Select("select id, username, age, gender, phone, delete_flag, create_time, update_time " +
2         "from user_info where username like concat('%',#{key},'%')")
3 List<UserInfo> queryAllUserByLike(String key);

使用内置函数 concat() 来进行字符串拼接 这样就可以使用#{}

二.数据库连接池

在上面Mybatis的讲解中,我们使用了数据库连接池技术,避免频繁的创建连接,销毁连接
下面我们来了解下数据库连接池

(一) 介绍

数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个.

在这里插入图片描述
没有使用数据库连接池的情况: 每次执行SQL语句,要先创建一个新的连接对象,然后执行SQL语句,SQL语句执行完,再关闭连接对象释放资源.这种重复的创建连接,销毁连接比较消耗资源

使用数据库连接池的情况: 程序启动时,会在数据库连接池中创建一定数量的Connection对象,当客户请求数据库连接池,会从数据库连接池中获取Connection对象,然后执行SQL,SQL语句执行完,再把Connection归还给连接池.

优点:

  1. 减少了网络开销
  2. 资源重用
  3. 提升了系统的性能

(二) 使用

常见的数据库连接池:

  • C3P0
  • DBCP
  • Druid
  • Hikari

目前比较流行的是 Hikari, Druid

1. Hikari : SpringBoot默认使用的数据库连接池

在这里插入图片描述

2. Druid

如果我们想把默认的数据库连接池切换为Druid数据库连接池,只需要引入相关依赖即可

1 <dependency>
2   <groupId>com.alibaba</groupId>
3   <artifactId>druid-spring-boot-3-starter</artifactId>
4   <version>1.2.21</version>
5 </dependency>

三.总结

(一) MySQL 开发企业规范

  1. **表名,字段名使用小写字母或数字,单词之间以下划线分割.尽量避免出现数字开头或者两个下划线中间只出现数字.**数据库字段名的修改代价很大,所以字段名称需要慎重考虑。
    MySQL 在 Windows 下不区分大小写,但在 Linux 下默认是区分大小写.因此,数据库名,表名,字段名都不允许出现任何大写字母,避免节外生枝
    正例: aliyun_admin,rdc_config,level3_name
    反例: AliyunAdmin,rdcConfig,level_3_name
  2. 表必备三字段: id, create_time, update_time
    id 必为主键, 类型为 bigint unsigned, 单表时自增, 步长为 1
    create_time, update_time 的类型均为 datetime 类型, create_time表示创建时间, update_time表示更新时间
    有同等含义的字段即可,字段名不做强制要求
  3. 在表查询中, 避免使用 * 作为查询的字段列表, 标明需要哪些字段(博客上给大家演示其实并不规范).
    1. 增加查询分析器解析成本
    2. 增减字段容易与 resultMap 配置不一致
    3. 无用字段增加网络消耗,尤其是 text 类型的字段

(二) #{}和${}区别

  1. #{}:预编译处理,${}:字符直接替换
  2. #{}可以防止SQL注入,${}存在SQL注入的风险,查询语句中,可以使用 #{},推荐使用 #{}
  3. 但是一些场景,#{} 不能完成,比如 排序功能,表名,字段名作为参数时,这些情况需要使用${}
  4. 模糊查询虽然${}可以完成,但因为存在SQL注入的问题,所以通常使用mysql内置函数concat来完成
http://www.dtcms.com/a/585824.html

相关文章:

  • 智能体AI的六大核心设计模式
  • 基于SLERP(Spherical Linear Interpolation) 进行旋转滤波
  • 站长工具seo查询5g5g成都市四方建设工程监理有限公司网站
  • 网站建设百科深圳网站建设公司fantodo
  • 接口自动化详细介绍
  • 深入解析多态:面向对象编程灵魂
  • 基于开源链动2+1模式AI智能名片S2B2C商城小程序的赛道力构建与品牌发展研究
  • 怎么做网站地图的样式wordpress网站后缀
  • 【报错解决】java:无效的目标发行版:17;源发行版17需要目标发行版17
  • C/C++输入输出初级(一) (算法竞赛)
  • java list<string> to string[] 怎么转换
  • 【Javaweb学习|黑马笔记|Day4】Web后端基础
  • 做智能网站系统重庆企业
  • Vue 项目实战《尚医通》,首页静态搭建 banner,笔记07
  • 构建AI智能体:八十八、大模型编辑:从一本百科全书到可修订的活页本
  • 2025.11.07 力扣每日一题
  • 网站建设 技术协议wordpress 文本框
  • pcl 构造线、平面、圆、球、圆柱体、长方体、圆锥体点云数据
  • m 的手机网站怎么做小俊哥网站建设
  • 电科金仓KingbaseES数据库全面语法解析与应用实践
  • 化妆品网站建设经济可行性分析好看的设计网站
  • 工程门户网站建设新桥做网站
  • 【开题答辩过程】以《割草机器人工作管理系统的设计与开发》为例,不会开题答辩的可以进来看看
  • 线束之插头导航器显示连接物功能文本
  • JVM(一)----- 类加载过程
  • 猎聘网网站谁做的东莞网页网站制作
  • Spring 6.x HTTP interface 使用说明
  • 庙行镇seo推广网站江西移动网站
  • C++ 图片加背景音乐的处理
  • 进度条+ 基础开发工具----版本控制器git 调试器gdb/cgdb