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子句、批量插入语句等

  1. collection:绑定方法参数中的集合,如 List,Set,Map 或数组对象。
  2. item:遍历时的每一个对象。
  3. open:语句块开头的字符串。
  4. close:语句块结束的字符串。
  5. 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> 标签下,可以看出,由于注解中是字符串拼接的方式,这种方法是非常容易出错的,而且排查错误也是有些困难的

主页 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/475173.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

网络安全,文明上网(2)加强网络安全意识

前言 在当今这个数据驱动的时代&#xff0c;对网络安全保持高度警觉已经成为每个人的基本要求。 网络安全意识&#xff1a;信息时代的必备防御 网络已经成为我们生活中不可或缺的一部分&#xff0c;信息技术的快速进步使得我们对网络的依赖性日益增强。然而&#xff0c;网络安全…

Notepad++--在开头快速添加行号

原文网址&#xff1a;Notepad--在开头快速添加行号_IT利刃出鞘的博客-CSDN博客 简介 本文介绍Notepad怎样在开头快速添加行号。 需求 原文件 想要的效果 方法 1.添加点号 Alt鼠标左键&#xff0c;从首行选中首列下拉&#xff0c;选中需要添加序号的所有行的首列&#xff…

sourceInsight常用设置和功能汇总(不断更新)(RGB、高亮、全路径、鼠标、宏、TODO高亮)

文章目录 必开配置设置背景颜色护眼的RGB值&#xff1f;sourceInsight4.0中如何设置选中某个单词以后自动高亮的功能&#xff1f;sourceinsight中输入设置显示全路径&#xff1f; 常用sourceInsight4.0中文乱码怎么解决&#xff0c;注意事项是什么&#xff1f;如何绑定鼠标中键…

【网络协议栈】网络层(中)IP地址的网段划分、CIDR划分以及网络层概念(内附手画分析图 简单易懂)

绪论​ “坚持的意义是&#xff0c;以后回想起来的时候&#xff0c;你会庆幸“真好&#xff0c;我撑过来了”&#xff0c;而不是后悔“要是当初再……就好了”。本章主要写道网络层中非常重要的概念&#xff0c;了解了网络中ip地址的由来&#xff0c;以及ip地址不够的如何的处理…

【Web03】Css的引用方式,Css的其他属性,Css进阶布局(水平)

CSS回顾 选择器 基本&#xff1a;标签选择器、ID选择器 标签选择器: 标签{}ID选择器:标签中定义ID属性。 #ID值{}重点&#xff1a;类选择器 标签中定义ID属性。 .类名{}Div与Span div——任意大小的长方形&#xff0c;大小css&#xff1a;width&#xff0c;height控制。–会换…

OpenHands:开源AI编程工具的新贵,让编程更自然

&#x1f680; AI技术在编程领域的应用正迅速发展&#xff0c;其中OpenHands作为一款新兴的开源AI编程工具&#xff0c;以其出色的性能和自然语言编程体验&#xff0c;成为了开发者的新宠。今天&#xff0c;让我们一起探索OpenHands的核心功能、架构设计&#xff0c;以及如何通…

【汇编语言】转移指令的原理(三) —— 汇编跳转指南:jcxz、loop与位移的深度解读

文章目录 前言1. jcxz 指令1.1 什么是jcxz指令1.2 如何操作 2. loop 指令2.1 什么是loop指令2.2 如何操作 3. 根据位移进行转移的意义3.1 为什么&#xff1f;3.2 举例说明 4. 编译器对转移位移超界的检测结语 前言 &#x1f4cc; 汇编语言是很多相关课程&#xff08;如数据结构…

Github客户端工具github-desktop使用教程

文章目录 1.客户端工具的介绍2.客户端工具使用感受3.仓库的创建4.初步尝试5.本地文件和仓库路径5.1原理说明5.2修改文件5.3版本号的说明5.4结合码云解释5.5版本号的查找 6.分支管理6.1分支的引入6.2分支合并6.3创建测试仓库6.4创建测试分支6.5合并分支6.6合并效果查看6.7分支冲…

最新智能AI问答运营系统(SparkAi)一站式AIGC系统,GPT-4.0/GPT-4o多模态模型+联网搜索提问+AI绘画+管理后台,用户会员套餐

目录 一、文章前言 系统介绍文档 二、功能模块介绍 系统快速体验 三、系统功能模块 3.1 AI全模型支持/插件系统 AI大模型 多模态模型文档分析 多模态识图理解能力 联网搜索回复 3.2 AI智能体应用 3.2.1 AI智能体/GPTs商店 3.2.2 AI智能体/GPTs工作台 3.2.3 自定义…

初识Linux · 线程概念

目录 前言&#xff1a; 线程的背景 线程的概念和Linux中的线程实现 线程杂谈 前言&#xff1a; Linux的学习从开始到现在&#xff0c;我们已经经历了许多大boss&#xff0c;从一开始的熟悉指令&#xff0c;到第一次在gcc环境下编译C语言的代码&#xff0c;到理解文件系统&…

ROS VRRP软路由双线组网方式

虚拟路由冗余协议 Virtual Router Redundancy Protocol (VRRP)&#xff0c;MikroTik RouteROS VRRP 协议遵循 RFC 2338。 VRRP 协议是保证访问一些资源不会中断&#xff0c;即通过多台路由器组成一个网关集合&#xff0c;如果其中一台路由器出现故障&#xff0c;会自动启用另外…

用 Python 与 Turtle 创作属于你的“冰墩墩”!

用 Python 与 Turtle 创作属于你的“冰墩墩”&#xff01; &#x1f980; 前言 &#x1f980;&#x1f40b; 效果图 &#x1f40b;&#x1f409; 代码 &#x1f409; &#x1f980; 前言 &#x1f980; 冰墩墩是2022年北京冬季奥林匹克运动会的官方吉祥物。以熊猫为原型&#x…

React 中使用 Axios 进行 HTTP 请求

下面是一个案例&#xff0c;展示如何在 React 中使用 Axios 进行 HTTP 请求&#xff0c;包括 GET 和 POST 请求的使用。 1. 安装 Axios 确保项目中已安装 Axios&#xff0c;可以通过以下命令安装&#xff1a; npm install axios2. 创建一个简单的 React 应用 项目结构&…

【GoogleChrome】在开发者工具中修改js、css并生效

以下网站有个登录验证cookie的js 按f12打开开发者工具&#xff0c;看到验证规则很简单 添加替换js的本地文件夹 允许权限 删除js并按ctrls保存&#xff0c;然后刷新页面&#xff0c;新js生效

CentOS操作系统下安装Nacos

CentOS下安装Nacos 前言 这在Centos下安装配置Nacos 下载Linux版Nacos 首先到Nacos的 Github页面&#xff0c;找到所需要安装的版本 也可以右键复制到链接&#xff0c;然后通过wget命令进行下载 wget https://github.com/alibaba/nacos/releases/download/1.3.2/nacos-ser…

数据结构--跳表

跳表 原理实现 原理 跳表&#xff08;skiplist&#xff09;是一种链表&#xff0c;而链表查询的时间复杂度为O(n)&#xff0c;为了优化查询效率&#xff0c;我们可以让每相邻两个节点升高一层&#xff0c;增加一个指针&#xff0c;让指针指向下下个节点&#xff1a; 这样所有…

【学术论文投稿】JavaScript 前端开发:从入门到精通的奇幻之旅

【中文核刊&普刊投稿通道】2024年体育科技与运动表现分析国际学术会议(ICSTPA 2024)_艾思科蓝_学术一站式服务平台 更多学术会议论文投稿请看&#xff1a;https://ais.cn/u/nuyAF3 目录 一、引言 二、JavaScript 基础 &#xff08;一&#xff09;变量与数据类型 &am…

云计算实训室建设的必要性

一、云计算发展的背景 云计算作为一种新型的信息技术服务模式&#xff0c;通过互联网提供动态易扩展且通常是虚拟化的资源&#xff0c;涵盖了从基础设施服务&#xff08;IaaS&#xff09;、平台服务&#xff08;PaaS&#xff09;到软件服务&#xff08;SaaS&#xff09;等多个…

鼠标绘制轮廓

需要对label进行提升&#xff0c;新建MyLabel类&#xff0c;并将其提升到label控件上&#xff0c;详见上篇控件提升 mylabelmouse.h #pragma once #include <QtWidgets/QMainWindow> #include "ui_mylabelmouse.h" #include <QMenu> #include "My…

LLM( Large Language Models)典型应用介绍 1 -ChatGPT Large language models

ChatGPT 是基于大型语言模型&#xff08;LLM&#xff09;的人工智能应用。 GPT 全称是Generative Pre-trained Transformer。-- 生成式预训练变换模型&#xff1a; Generative&#xff08;生成式&#xff09;&#xff1a;可以根据输入生成新的文本内容&#xff0c;例如回答问题…