《#{} vs ${}:MyBatis 里这俩符号,藏着性能与安全的 “生死局”》

1.多表查询

多表查询和单表查询类似,只是SQL不同⽽已

数据准备:

对应Model:

数据查询

SQL:

补充实体类:

接⼝定义:

  1. 字段与实体类属性不一致的解决方式 :当数据库字段名与实体类属性名不匹配时,可通过ResultMap(自定义映射关系)或 SQL 别名(如SELECT user_name AS userName)来对齐,这一规则同时适用于单表和多表查询。

  2. MyBatis 的核心组成:MyBatis 处理查询(无论单表 / 多表)的核心逻辑由三部分构成:

    • SQL:执行数据查询的语句;
    • 映射关系:关联数据库结果与实体类的规则(如字段 - 属性的对应);
    • 实体类:承载查询结果的数据对象。
  3. 映射关系的作用:通过定义映射规则,将 SQL 执行后的结果集(数据库字段)与实体类的属性关联,实现数据的自动填充。

2.#{}和${}

2.1#{}和${}使⽤

Integer 类型参数:#{} vs ${}

1. #{}的执行(预编译,安全)

代码

执行逻辑: MyBatis 将#{id}替换为?占位符,生成预编译 SQL

  • 参数(如id=1)通过PreparedStatement传入?,日志中体现为Parameters: 1(Integer),最终安全执行。
2. ${}的执行(直接拼接,无语法错误但有风险)

代码

执行逻辑:参数直接拼入 SQL,生成即时 SQL

  • 因为id是 Integer 类型,拼接后 SQL 语法合法,能正常执行,但存在 SQL 注入风险 (比如参数传入**1 or 1=1,会查询全表**)。

String 类型参数:#{} vs ${}(差异更明显)

1. #{}的执行(自动加引号,安全)

代码

执行逻辑:生成预编译 SQL

  • 参数(如name="zhangsan")传入时,MyBatis 会自动添加单引号,最终执行的 SQL 等价于:

语法合法,且安全。

2. ${}的执行(需手动加引号,否则报错)

未加引号的错误情况

代码:

拼接后 SQL:

数据库会把zhangsan当作字段名(而非字符串值),导致 "Unknown column 'zhangsan'" 语法错误。

手动加引号的正确(但仍有风险)情况

代码(补充单引号):

拼接后 SQL:

语法合法,但仍存在 SQL 注入风险 (比如参数传入zhangsan' or '1'='1,会拼接出username = 'zhangsan' or '1'='1'****,查询全表)。

核心总结:#{}和${}的本质差异

维度 #{} ${}
参数处理方式 预编译占位符(?),自动适配类型(加引号) 字符串直接拼接,需手动处理类型(如String 加引号
安全特性 防 SQL 注入 易被 SQL 注入
语法兼容性 自动适配所有参数类型 需手动处理类型,否则报错
推荐场景 所有普通参数传递 仅动态表名 / 字段名(需严格校验参数)

2.2#{}和${}区别

区别就是预编译SQL和即时SQL的区别.

1.SQL 执行流程:即时 SQL vs 预编译 SQL

1.1 即时 SQL(对应${})的执行流程

当客户端发送 SQL 到服务器,需经历 3 个完整步骤:

  1. 语法 / 语义解析:校验 SQL 语句是否合法;
  2. SQL 优化:数据库生成最优执行计划;
  3. 执行并返回结果 。每次执行都要重复上述流程,效率较低
1.2 预编译 SQL(对应#{})的执行流程

预编译 SQL 会将SQL 结构(如select * from userinfo where id=? 先编译一次,编译后的结果会被缓存 ;后续执行时,仅需传入参数 (替换?),跳过 "解析、优化" 步骤,直接执行,效率更高。

2.#{}的核心优势 1:性能更高

实际业务中,同一条 SQL(仅参数不同)会被反复执行(比如多次根据不同id查询用户):

  • 若用${}(即时 SQL):每次都要重新解析、优化,性能损耗大;
  • 若用#{}(预编译 SQL):仅首次编译,后续复用缓存,大幅提升效率。

3.#{}的核心优势 2:防止 SQL 注入

3.1.SQL 注入的定义

通过构造恶意参数 ,修改原有 SQL 的逻辑,达到非法操作数据库的目的(比如越权查询、删除数据)。

3.2${}引发 SQL 注入的案例

以 "根据用户名查询" 为例:

代码(用${}

正常参数 :传入"admin",拼接后 SQL 为:

仅查询admin的信息,符合预期。

恶意参数 :传入"' or 1=1'",拼接后 SQL 为:

1=1是恒成立的条件,最终会查询全表用户信息,造成数据泄露。

3.3 #{}避免 SQL 注入的原理

#{}会将参数作为 "值" 传入预编译 SQL 的?占位符,即使传入恶意参数(如"' or 1=1'"),也会被当作普通字符串值处理,最终执行的 SQL 等价于:

数据库会去查询username等于' or 1=1的用户(实际不存在),不会修改 SQL 逻辑,从而避免注入。

4.SQL 注入的风险场景拓展

若用${}处理登录场景的用户名 / 密码:

代码:

恶意参数:密码传入"' or 1=1'",拼接后 SQL 为:

条件恒成立,无需正确密码即可登录成功,造成账号被盗。

核心结论
特性 #{} ${}
字符串参数处理 自动加单引号,无需手动加 无自动加引号,String 类型需手动加单引号
SQL 注入防护 注入语句被包裹在单引号中,成普通字符串 注入语句直接拼接,修改 SQL 逻辑
典型坑点 手动加引号会导致''注入内容'',查不到值 忘加引号会报 "未知字段",加了仍有注入风险
  1. #{}:啥都不用加,MyBatis 帮你搞定类型 + 引号,安全又省心;
  2. ${}:String 类型手动加单引号,但仅用于动态表名 / 字段名,且必须校验参数;
  3. 避坑:#{}别加引号,${}非必要不用。
补充 2 个易踩坑细节
1. 手动给#{}加引号的反例

传入zhangsan时,#{name}自动加单引号 变成'zhangsan',再拼接手动引号,最终 SQL:

结果:数据库会找username等于''zhangsan''(带双单引号)的用户,实际不存在,返回空。

2. 数字类型也可能被注入

数字类型用${}仍有注入风险:

传入恶意参数1 or 1=1(注意:实际传参是字符串形式的数字表达式),拼接后 SQL:

结果:查询全表数据,数字类型也能被注入(只是 String 类型注入场景更多)。

3.#{}${}排序场景Like 查询场景中的适用

一、排序场景:必须用${},不能用#{}

(1)场景需求

实现 "按 id 升序 / 降序排序",排序规则(asc/desc)作为参数传入。

(2)#{}的问题

代码(错误示例):

执行时,#{sort}会将 String 类型的sort参数自动加单引号,生成 SQL:

错误原因:SQL 中排序规则(asc/desc)是关键字,不能加单引号,因此会报语法错误。

(3)${}的正确用法

代码(正确示例):

执行时,${sort}直接拼接参数,生成 SQL:

  • 注意:需对sort参数做白名单校验 (仅允许asc/desc),避免注入风险。

二、Like 查询场景:用#{}+concat()解决安全问题

(1)直接用#{}的问题

代码(错误示例):

执行时,#{key}自动加单引号,生成 SQL:

  • 错误原因:单引号嵌套导致语法错误。
(2)直接用${}的风险

代码(有注入风险):

  • 虽然能生成正确 SQL(like '%zhangsan%'),但${key}拼接恶意参数 (如**zhangsan' or 1=1 --**),导致 SQL 注入。
(3) 安全解决方案:#{}+concat()函数

利用 MySQL 的concat()函数拼接模糊查询的通配符 ,同时保留#{}的安全性:代码(正确示例):

执行时,#{key}会被替换为?concat()自动拼接通配符,生成预编译 SQL:

  • 既避免了语法错误,又防止了 SQL 注入是 Like 查询的推荐写法

三、总结:${}的合法使用场景 + 安全规范

适用场景 原因 安全规范
动态排序规则(asc/desc 排序关键字不能加单引号 对参数做白名单校验
动态表名 / 字段名 表名 / 字段名是 SQL 结构的一部分 对参数做白名单校验
Like 查询(禁止用${} 存在注入风险 #{}+concat()替代

这些场景的核心逻辑是:${}仅用于 "SQL 结构的动态部分"(如关键字、表名),且必须校验参数;数据值的传递一律用#{}

MyBatis初阶至此讲解结束

一、数据库连接池的核心概念

数据库连接池是管理数据库连接的容器,其核心逻辑是:

  • 提前创建一定数量的数据库连接,存入 "连接池" 中;
  • 应用程序需要访问数据库时,直接从池里复用已有的连接,而非重新创建;
  • 用完连接后,将其归还到池里,供其他请求使用。

二、有无连接池的对比

场景 无连接池 有连接池
连接方式 每次请求都新建 / 销毁连接 复用池中的已有连接
性能 新建 / 销毁连接开销大,性能低 避免重复开销,性能高
资源消耗 频繁创建连接会占用大量系统资源 连接数量可控,资源消耗更稳定

三、连接池的核心优势

  1. 减少网络 / 系统开销:避免了 "频繁创建 / 销毁数据库连接" 的高成本操作;
  2. 资源复用:连接可重复使用,降低资源浪费;
  3. 提升系统性能:连接获取更高效,支持更高的并发请求。

四、常用数据库连接池及 SpringBoot 中的使用

1. 主流连接池
  • C3P0/DBCP:早期常用,但性能和功能不及现代连接池,逐渐被替代;
  • Hikari :SpringBoot 默认连接池,日语意为 "光",以极致性能为目标,轻量高效;
  • Druid:阿里巴巴开源,功能丰富(自带监控、防 SQL 注入等),是企业级场景的常用选择。
2. SpringBoot 中使用连接池
  • Hikari(默认) :无需额外配置,SpringBoot 自动集成,启动日志会显示HikariDataSource
  • Druid(切换方式):需引入对应依赖(根据 SpringBoot 版本选择):

SpringBoot 3.x:

SpringBoot 2.x:

五、MySQL 开发企业规范

1. 表名、字段名命名规则
  • 规范小写字母 + 数字,单词用下划线分隔;禁止大写字母、数字开头、连续下划线。
  • 原因 :MySQL 在 Linux 下区分大小写,统一小写可避免环境兼容问题;字段修改成本高,命名需谨慎。
  • 正例:aliyun_adminrdc_config
  • 反例:AliyunAdmin(大写)、level__name(连续下划线)。
2. 表必备字段

所有表需包含 3 个基础字段:

  • id:主键,类型为bigint unsigned,单表时自增(步长 1);
  • create_time:创建时间,类型datetime
  • update_time:更新时间,类型datetime。(字段名可根据同义调整,如gmt_create替代create_time
3. 查询规范:禁止用*查询
  • 风险:增加解析成本、易与 ResultMap 配置不一致、无用字段(如text)增加网络消耗;
  • 要求:明确指定需要查询的字段。
相关推荐
2501_918126912 小时前
国标麻将一抽胡
前端·学习·html·个人开发
步步为营DotNet2 小时前
深度探究.NET中WeakReference:灵活内存管理的利器
java·jvm·.net
a程序小傲2 小时前
中国邮政Java面试被问:Kafka的Log Compaction实现和删除策略
java·开发语言·后端·python·面试·职场和发展·kafka
hopsky2 小时前
数据服务开源-SqlRest 1.6 idea中启动 (pg版)
java·ide·intellij-idea
indexsunny2 小时前
互联网大厂Java面试实战:音视频场景中的Spring Boot与Kafka技术问答
java·spring boot·redis·面试·kafka·spring security·互联网大厂
岁岁种桃花儿2 小时前
Spring Boot @GetMapping注解:从应用到原理深度解析
java·spring boot·后端
颜淡慕潇2 小时前
Spring Boot 3.x 升级实战:3.0 → 3.5:为什么升、升什么、以及我们是怎么升的
java·spring boot·后端
地球资源数据云2 小时前
1960年-2024年中国农村居民消费价格指数数据集
大数据·数据库·人工智能·算法·数据集
石像鬼₧魂石2 小时前
补充章节:WPScan 实战后的 “打扫战场 + 溯源” 流程
数据库·学习·mysql