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代码…...

单片机进阶硬件部分_day2_项目实践
设计要求 从绘制原理图到画PCB板,完成智能云衣柜项目 STM32 (Modbus)云IOT衣物云端管理 华为PCB布线规范 基于IoT的智享家主控系统 步骤分析 需求分析 器件选型绘制原理图(器件连接)PCB布局、布线泪滴、铺铜、添加丝印…...

labview关于文件路径的问题
在调用文件或拆分文件的时候经常会用到拆分路径函数和创建路径函数,最常用的也是当前应用程序目录或者是当前VI目录。 这里我们看到应用程序目录和VI目录在同一项目中,应用程序目录更像是根目录,往下拆分成了各个VI的子目录。 接下来我们来拆…...

72项!湖北省2024年度第二批省级科技计划项目拟立项项目公示!
本期精选 SCI&EI ●IEEE 1区TOP 计算机类(含CCF); ●EI快刊:最快1周录用! 知网(CNKI)、谷歌学术期刊 ●7天录用-检索(100%录用),1周上线; 免费稿件评估 免费匹配…...
神经网络问题之:梯度不稳定
梯度不稳定是深度学习中,特别是在训练深度神经网络时常见的一个问题,其本质涉及多个方面。 一、根本原因 梯度不稳定问题的根本原因在于深度神经网络的结构和训练过程中的一些固有特性。随着网络层数的增加,梯度在反向传播过程中会逐层累积变…...

ORACLE删不掉job,如何解决。
问题: 删掉 NYZSM 时出错: ORA-27478: 作业 “ZHY.NYZSM” 正在运行 ORA-06512: 在 “SYS.DBMS_ISCHED”, line 213 ORA-06512: 在 “SYS.DBMS_SCHEDULER”, line 657 ORA-06512: 在 line 2 1、停止作业: 使用DBMS_SCHEDULER.STOP_JOB过程来…...

可视化建模与UML《活动图实验报告》
你当像鸟飞往你的山。 一、实验目的: 1、熟悉活动图的基本功能和使用方法。 2、掌握使用建模工具软件绘制协作图的方法 二、实验环境: window7 | 10 | 11 EA15 三、实验内容: <1>绘制学生选课系统中添加课程(Add Course)用例的活动图…...

基于 MUSA 的大语言模型推理和服务框架vLLM
1. 引言 vLLM是一个高性能且内存高效的大语言模型推理和服务框架,也是当前业界使用范围最广的大模型推理框架,截至目前github star数28.4k。该框架性能优秀,而且部署容易,使用CUDA/ROCm提供GPU加速能力。但vLLM目前不支持使用摩…...

鸿蒙网络编程系列48-仓颉版UDP回声服务器示例
1. UDP回声服务器简介 回声服务器指的是这样一种服务器,它接受客户端的连接,并且把收到的数据原样返回给客户端,本系列的第2篇文章《鸿蒙网络编程系列2-UDP回声服务器的实现》中基于ArkTS语言在API 9的环境下实现了UDP回声服务器,…...

android-studio-4.2下载 、启动
下载 分享一个国内的android studio网站,可以下载SDK和一些Android studio开发工具 https://www.androiddevtools.cn/ 启动 JAVA_HOME/app/zulu17.48.15-ca-jdk17.0.10-linux_x64/ /app5/android-studio-home/android-studio-ide-201.6568795-linux-4.2C1/bin/s…...

深度学习day2-Tensor 2
六 Tensor常见操作 Tensor:多维数组,用于存储和操作数据 1 获取元素值 data.item():单个元素tensor转为python数值 import torch #标量 xtorch.tensor(1) print(x.item()) #一阶 xtorch.tensor([100]) print(x.item()) #如果输入的数据超过1个&#…...