MyBatis——#{} 和 ${} 的区别和动态 SQL
1. #{} 和 ${} 的区别
为了方便,接下来使用注解方式来演示:
#{} 的 SQL 语句中的参数是用过 ? 来起到类似于占位符的作用,而 ${} 是直接进行参数替换,这种直接替换的即时 SQL 就可能会出现一个问题
当传入一个字符串时,就会发现 SQL 语句出错了:
这里的 zhangsan并不是作为一个字符串使用的,应该是加上引号的
加上之后就可以正常查询了
这就可能会出现 SQL 注入的问题
来看一下 SQL 注入的例子,假如传入的参数是' or 1='1
@Select("select username, `password`, age, gender, phone from user_info where username= '${name}' ")
List<UserInfo> queryByName(String name);
按道理说是没有这个用户的,但是却把所有用户的信息都查出来了
如果在某些登录的界面输入 SQL 注入代码' or 1='1
就可能登录成功
使用 #{} 就没有这个问题
除了以上的区别外,二者还有性能方面的区别
在上面提到过,#{} 是预编译 SQL,${} 是即时 SQL ,预编译SQL编译一次之后会将编译后的 SQL 语句缓存起来,后面再执行这条语句时,不会再次编译,省去了解析优化等过程,以此来提高效率,所以当需要频繁地使用 SQL 语句时,预编译的性能优化就体现出来了,而对于即时 SQL ,如果只是在启动时或者很少变化的场景下使用${}
来配置一些数据库对象名称等,它可以避免预编译的过程,执行起来相对直接
2. 排序
在上面看来,#{}
无论是在安全性还是效率上,都占据了优势,那么都是用 #{}
可以吗?
来看使用 #{}
来实现排序功能:
@Select("select * from user_info order by id #{order}")
List<UserInfo> selectUserByOrder(String order);
这里把排序的方式作为参数,给用户选择是升序还是降序排序,测试方法中传入一个字符串表示降序
@Test
void selectUserByOrder() {userInfoMapper.selectUserByOrder("desc");
}
然后就会发现报错了,可以看到 "desc" 确实是当做字符串传进去了,#{} 的方式会把字符串类型加上单引号,然后 SQL 语句就会变成这样:
select * from user_info order by id 'order'
这样肯定是不对的,那么这个时候就需要用到 ${} 了,直接进行参数替换,但是使用 ${} 肯定就需要考虑 SQL 注入的问题,由于排序方式只有 asc 和 desc 两种方式,可以采用枚举类来进行校验,也可以通过判断条件来实现校验
3. 模糊查询
通过模糊查询来查找名字中含有“zhang”的信息
@Select("select * from user_info where username like '%#{name}%'")
List<UserInfo> selectUserByLike(String name);
@Test
void selectUserByLike() {System.out.println(userInfoMapper.selectUserByLike("zhang"));
}
然后发现又报错了,因为使用的是 #{} ,所以就会替换为 '%'zhang'%',这样是肯定不能运行的,所以还是需要使用 ${} 进行直接替换,但是这时怎么去解决 SQL 注入的问题呢,这样就不能简单的通过枚举或者判断来约束传入的参数了,这时就可以通过使用拼接的方式
通过 CONCAT 函数来对 SQL 语句进行拼接,这样就可以使用 #{},
@Select("select * from user_info where username like CONCAT('%',#{name},'%')")
List<UserInfo> selectUserByLike(String name);
4. 数据库连接池
在传统的数据库访问模式中,每当应用程序需要与数据库进行交互时,它会创建一个新的数据库连接,使用完毕后关闭连接,这样频繁地创建和销毁数据库连接会消耗大量的系统资源
数据库连接池的出现就是为了解决这些问题。它在应用程序启动时预先创建一定数量的数据库连接,将这些连接存储在一个 “池” 中。当应用程序需要访问数据库时,从池中获取一个可用的连接,使用完毕后将连接归还给池,而不是直接关闭连接,从而避免了频繁创建和销毁连接所带来的性能开销,这一点和线程池是类似的
常见的数据库连接池有:C3P0 , DBCP , Druid , Hikari
Spring Boot 默认使用的是 Hikari
如果想更换为 Druid 的话,导入相关的依赖即可
<dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-3-starter</artifactId><version>1.2.21</version>
</dependency>
然后再启动程序之后就更换为了 Druid
也可以去 官方文档 进行查看
5. 动态 SQL
我们在填一些表单的时候应该会见到下面这种,有的是必填项,有的是选填项,对于选填项来说,如果没有填,肯定是需要赋一个默认值的,比如 null,那么就需要动态 SQL 来实现这样的功能
5.1. <if>
可以通过 if 标签来实现一下:
@Mapper
public interface UserInfoXmlMapper {Integer insertUserByCondition(UserInfo userInfo);
}
再来看 XML 中的 SQL 语句
<insert id="insertUserByCondition">insert into user_info(username,'password',age,<if test="gender != null">gender</if>)values (#{username},#{password},#{age},<if test="gender != null">#{gender}</if>)
</insert>
if 标签中的参数和 java 对象中的属性参数是对应的
@Test
void insertUserByCondition() {UserInfo userInfo = new UserInfo();userInfo.setUsername("java");userInfo.setPassword("java");userInfo.setAge(19);//userInfo.setGender(1);Integer integer = userInfoXmlMapper.insertUserByCondition(userInfo);
}
如果不传入性别的话来看一下结果:
由于性别没有传入,所以说 SQL 语句中是只有前三个参数的,所以第三个参数那里就多了一个逗号,导致最终的 SQL 的语法错误
那么就可以想一个办法,如果把逗号直接加前面,是不是就可以解决了
这样看似是可以解决的,但是如果说 username, age 都设为了非必填的,例如 username 没有传入参数,但是 age 传入了参数,这样前面就多了一个逗号,这时 SQL 语句就又会出错了,把逗号都加到右边,也是会出现问题的
这时就需要用到下面的标签了
5.2. <trim>
主要用于去除 SQL 语句中多余的关键字或者字符,同时也可以添加自定义的前缀和后缀
・prefix:用于为包含在trim
标签内部的 SQL 语句块添加一个前缀
・suffix:表示整个语句块,以 suffix 的值作为后缀.
・prefixOverrides:为trim
标签内的 SQL 语句块添加一个后缀.
・suffixOverrides:表示整个语句块要去除掉的后缀.
<insert id="insertUserByCondition">insert into user_info<trim prefix="(" suffix=")" suffixOverrides=","><if test="username!=null">username ,</if><if test="password!=null">`password`,</if><if test="age!=null">age,</if><if test="gender!=null">gender</if></trim>values<trim prefix="(" suffix=")" suffixOverrides=","><if test="username!=null">#{username},</if><if test="password!=null">#{password},</if><if test="age!=null">#{age},</if><if test="gender!=null">#{gender}</if></trim>
</insert>
这就表示在 SQL 语句前面加上一个 '(' ,后面加上 ')' ,如果最后是以逗号结尾的就把逗号删了,以此来实现 SQL 语句拼接的效果
5.3. <where>
来看一下条件查询
这里的 and 和上面的逗号是一样的性质,放在右边或者左边都不合适,还是可以使用 trim 标签来解决
但是这时其实还有一个问题,如果说 age 和 deleteFlag 都没有传入的话,最后的 SQL 语句 where 后面就没有了,这时又会报错了
这种情况 trim 就解决不了了,其中一种解决方式是在 where 后面加上 1=1,那么 and 就需要加在前面了:
比较推荐的写法就是使用 <where> 标签
<select id="selectUserByCondition" resultType="com.example.mybatisdemo.model.UserInfo">select * from user_info<where><if test="age!=null">and age=#{age}</if><if test="deleteFlag!=null">and delete_flag = #{deleteFlag}</if></where>
</select>
<where> 标签如果后面都没有值的话,SQL 语句中的 where 也不会添加,并且如果只有一个值的话,前面的 and 也会被去掉,也不用 trim 标签了,不过去掉的是前面的 and,写后面是不会去掉的
5.4. <set>
动态更新操作也是,当后面有值的时候就更新,没有值的时候就不更新,<set> 标签的作用和 where 类似,也是后面有值的话就生成 set 关键字并且去除右边的逗号,但是后面设置的内容也不能全部是空,此时就算没有生成 set 标签,但是前面还有一个 update 关键字,最后的 SQL 语句还是有问题
<update id="updateByCondition">update user_info<set><if test="username!=null">username = #{username},</if><if test="password!=null">password = #{password},</if><if test="gender!=null">gender = #{gender}</if></set><where>id = #{id}</where>
</update>
5.5. <foreach>
foreach 用于在 SQL 语句中遍历集合,动态地构建包含多个参数的 SQL 语句,比如IN
子句、批量插入语句等
- collection:绑定方法参数中的集合,如 List,Set,Map 或数组对象。
- item:遍历时的每一个对象。
- open:语句块开头的字符串。
- close:语句块结束的字符串。
- separator:每次遍历之间间隔的字符串。
<delete id="batchDelete">delete from user_info where id in<foreach collection="ids" separator="," item="id" open="(" close=")">#{id}</foreach>
</delete>
5.6. <include>
<include>
标签主要用于代码复用。它可以将一个 SQL 片段(通常是在<sql>
标签中定义的)包含到另一个 SQL 语句中,使得 SQL 语句的编写更加模块化,减少重复代码
例如上面的重复语句就可以提取出来
<sql id="insertCol">insert into user_info(username, password, age, gender)
</sql>
然后就可以通过 include 标签来引用了
6. 注解方式的动态 SQL
注解方式就是把原来 XML 中的 SQL 语句部分写到注解的 <script> 标签下,可以看出,由于注解中是字符串拼接的方式,这种方法是非常容易出错的,而且排查错误也是有些困难的
主页
相关文章:

MyBatis——#{} 和 ${} 的区别和动态 SQL
1. #{} 和 ${} 的区别 为了方便,接下来使用注解方式来演示: #{} 的 SQL 语句中的参数是用过 ? 来起到类似于占位符的作用,而 ${} 是直接进行参数替换,这种直接替换的即时 SQL 就可能会出现一个问题 当传入一个字符串时ÿ…...
解决sql字符串
根据你描述的情况以及调试截图中的内容,我可以确认你的 sql 字符串在 Python 中由于转义字符的问题,可能导致在 Oracle 中运行时出错。 以下是一些排查和修改建议: 问题分析 转义字符问题: 在调试界面中可以看到,DEC…...

深度解析:Android APP集成与拉起微信小程序开发全攻略
目录 一、背景以及功能介绍 二、Android开发示例 2.1 下载 SDK 2.2 调用接口 2.3 获取小程序原始Id 2.4 报错提示:bad_param 2.4.1 错误日志 2.4.2 解决方案 相关推荐 一、背景以及功能介绍 需求:产品经理需要APP跳转到公司的小程序(最好指定页…...

Leetcode 被围绕的区域
算法思想(解题思路): 这道题的核心是 将所有被边界包围的 O 保留下来,而将其他被围绕的 O 转换为 X。为了实现这一目标,我们可以分三步完成: 第一步:标记边界及其相连的 O 为特殊标记ÿ…...

ssm框架-spring-spring声明式事务
声明式事务概念 声明式事务是指使用注解或 XML 配置的方式来控制事务的提交和回滚。 开发者只需要添加配置即可, 具体事务的实现由第三方框架实现,避免我们直接进行事务操作! 使用声明式事务可以将事务的控制和业务逻辑分离开来,提…...
React第五节 组件三大属性之 props 用法详解
特性 a、props最好是仅限于父子上下级之间的数据传递,如果是祖孙多级之间传递属性,可以考虑使用props是否合适,或者使用替代方案 useContext() 或者使用 redux状态管理; b、props 中的属性是只读属性,如果想修改其中的…...

测评部署和管理 WordPress 最方便的面板
新版宝塔面板快速搭建WordPress新手教程 - 倚栏听风-Morii - 博客园 初学者使用1Panel面板快速搭建WordPress网站 - 倚栏听风-Morii - 博客园 可以看到,无论是宝塔还是1Panel,部署和管理WordPress都有些繁琐,而且还需要额外去配置Nginx和M…...
【系统分析师】-2024年11月论文-论DevOps开发
1、题目要求 论Devops及其应用。Devops是一组过程、方法与系统的统称,用于促进开发、技术运营和质量保障部门之间的沟通,协作与整合。它是一种重视软体开发人员和工厂运维技术人员之间沟通合作的模式。透过自动化“软件交付”和“架构变更”的流程&…...
算法【子数组最大累加和问题与扩展】
子数组最大累加和问题是一个非常经典的问题,也比较简单。但是扩展出的问题很多,在笔试、面试中特别常见,扩展出的问题很多非常有趣,解法也比较巧妙。 下面通过一些题目来加深理解。 题目一 测试链接:https://leetcode…...
小程序23-页面的跳转:navigation 组件详解
小程序中,如果需要进行跳转,需要使用 navigation 组件,常用属性: 1.url :当前小程序内的跳转链接 2.open-type:跳转方式 navigate:保留当前页面,跳转应用内的某个页面,…...

AI社媒引流工具:解锁智能化营销的新未来
在数字化浪潮的推动下,社交媒体成为品牌营销的主战场。然而,面对海量的用户数据和日益复杂的运营需求,传统营销方法显得力不从心。AI社媒引流王应运而生,帮助企业在多平台中精准触达目标用户,提升营销效率和效果。 1.…...

【Node.js】全面解析 Node.js 安全最佳实践:保护您的应用
Node.js 是一种强大的 JavaScript 运行时,广泛用于构建现代 Web 应用和 API。然而,由于其开放性和异步特性,Node.js 应用容易受到多种安全威胁的攻击,比如 SQL 注入、跨站脚本 (XSS) 和拒绝服务攻击 (DoS)。在本文中,我…...

Docker 用法详解
文章目录 一、Docker 快速入门1.1 部署 MYSQL1.2 命令解读: 二、Docker 基础2.1 常见命令:2.1.1 命令介绍:2.1.2 演示:2.1.3 命令别名: 2.2 数据卷:2.2.1 数据卷简介:2.2.2 数据卷命令ÿ…...

Python小游戏28——水果忍者
首先,你需要安装Pygame库。如果你还没有安装,可以使用以下命令进行安装: 【bash】 pip install pygame 《水果忍者》游戏代码: 【python】 import pygame import random import sys # 初始化Pygame pygame.init() # 设置屏幕尺寸 …...
Kafka Offset 自动提交和手动提交 - 漏消费与重复消费
目录 1. 引言 2. Offset 提交方式概述 2.1 自动提交 Offset 2.2 手动提交 Offset 3. 漏消费与重复消费的问题分析 3.1 自动提交模式下的漏消费和重复消费 漏消费 重复消费 3.2 手动提交模式下的漏消费和重复消费 漏消费 重复消费 4. 自动提交与手动提交的选择 4.1…...
Vue3父组件和子组件
子组件暴露方法给父组件,父组件传值 子组件 const editCalendar (value: string) > {console.log(获取父组件的值, value)};//暴露给外部调用defineExpose({editCalendar,}); 父组件 <template> <CalendarEdit ref"editRef" /> </…...
Linux 定时任务全解析
文章目录 一、Cron 服务1.1安装1.2配置文件格式1.3使用方法1.4系统级与用户级 Cron 任务区别 二、At 服务2.1安装2.2工作原理2.3使用方法 一、Cron 服务 1.1安装 在大多数 Linux 发行版中,Cron 服务通常已经默认安装。例如在 Ubuntu 系统中,可以通过以…...
XLNet——打破 BERT 局限的预训练语言模型
近年来,深度学习在自然语言处理(NLP)领域取得了革命性进展,其中 BERT 的出现标志着双向语言建模的强大能力。然而,BERT 也存在一些局限性,限制了其在生成任务中的表现。2019 年,由 Google 和 Ca…...

开源代码统计工具cloc的简单使用
一.背景 公司之前开发了个小系统,要去申请著作权,需要填写代码数量。应该怎么统计呢?搜索了一下,还是用开源工具cloc吧!我的操作系统是windows,代码主要是java项目和vue项目。 二.到哪里找 可以去官方下载…...

如何创建一个项目用于研究element-plus的原理
需求:直接使用element-plus未封装成组件的源码,创建一个项目,可以使用任意的element-plus组件,可以深度研究组件的运行。例如研究某一个效果,如果直接在node_modules修改elment-plus打包之后的那些js、mjs代码…...
React 第五十五节 Router 中 useAsyncError的使用详解
前言 useAsyncError 是 React Router v6.4 引入的一个钩子,用于处理异步操作(如数据加载)中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误:捕获在 loader 或 action 中发生的异步错误替…...

日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻
在如今就业市场竞争日益激烈的背景下,越来越多的求职者将目光投向了日本及中日双语岗位。但是,一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧?面对生疏的日语交流环境,即便提前恶补了…...
进程地址空间(比特课总结)
一、进程地址空间 1. 环境变量 1 )⽤户级环境变量与系统级环境变量 全局属性:环境变量具有全局属性,会被⼦进程继承。例如当bash启动⼦进程时,环 境变量会⾃动传递给⼦进程。 本地变量限制:本地变量只在当前进程(ba…...
Cesium1.95中高性能加载1500个点
一、基本方式: 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...

Docker 运行 Kafka 带 SASL 认证教程
Docker 运行 Kafka 带 SASL 认证教程 Docker 运行 Kafka 带 SASL 认证教程一、说明二、环境准备三、编写 Docker Compose 和 jaas文件docker-compose.yml代码说明:server_jaas.conf 四、启动服务五、验证服务六、连接kafka服务七、总结 Docker 运行 Kafka 带 SASL 认…...

vscode(仍待补充)
写于2025 6.9 主包将加入vscode这个更权威的圈子 vscode的基本使用 侧边栏 vscode还能连接ssh? debug时使用的launch文件 1.task.json {"tasks": [{"type": "cppbuild","label": "C/C: gcc.exe 生成活动文件"…...

visual studio 2022更改主题为深色
visual studio 2022更改主题为深色 点击visual studio 上方的 工具-> 选项 在选项窗口中,选择 环境 -> 常规 ,将其中的颜色主题改成深色 点击确定,更改完成...
在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module
1、为什么要修改 CONNECT 报文? 多租户隔离:自动为接入设备追加租户前缀,后端按 ClientID 拆分队列。零代码鉴权:将入站用户名替换为 OAuth Access-Token,后端 Broker 统一校验。灰度发布:根据 IP/地理位写…...

2021-03-15 iview一些问题
1.iview 在使用tree组件时,发现没有set类的方法,只有get,那么要改变tree值,只能遍历treeData,递归修改treeData的checked,发现无法更改,原因在于check模式下,子元素的勾选状态跟父节…...

Mac软件卸载指南,简单易懂!
刚和Adobe分手,它却总在Library里给你写"回忆录"?卸载的Final Cut Pro像电子幽灵般阴魂不散?总是会有残留文件,别慌!这份Mac软件卸载指南,将用最硬核的方式教你"数字分手术"࿰…...