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

自然语言处理——循环神经网络
自然语言处理——循环神经网络 循环神经网络应用到基于机器学习的自然语言处理任务序列到类别同步的序列到序列模式异步的序列到序列模式 参数学习和长程依赖问题基于门控的循环神经网络门控循环单元(GRU)长短期记忆神经网络(LSTM)…...
现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?
现有的 Redis 分布式锁库(如 Redisson)相比于开发者自己基于 Redis 命令(如 SETNX, EXPIRE, DEL)手动实现分布式锁,提供了巨大的便利性和健壮性。主要体现在以下几个方面: 原子性保证 (Atomicity)ÿ…...

云原生安全实战:API网关Kong的鉴权与限流详解
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关(API Gateway) API网关是微服务架构中的核心组件,负责统一管理所有API的流量入口。它像一座…...
如何通过git命令查看项目连接的仓库地址?
要通过 Git 命令查看项目连接的仓库地址,您可以使用以下几种方法: 1. 查看所有远程仓库地址 使用 git remote -v 命令,它会显示项目中配置的所有远程仓库及其对应的 URL: git remote -v输出示例: origin https://…...
LangChain【6】之输出解析器:结构化LLM响应的关键工具
文章目录 一 LangChain输出解析器概述1.1 什么是输出解析器?1.2 主要功能与工作原理1.3 常用解析器类型 二 主要输出解析器类型2.1 Pydantic/Json输出解析器2.2 结构化输出解析器2.3 列表解析器2.4 日期解析器2.5 Json输出解析器2.6 xml输出解析器 三 高级使用技巧3…...
验证redis数据结构
一、功能验证 1.验证redis的数据结构(如字符串、列表、哈希、集合、有序集合等)是否按照预期工作。 2、常见的数据结构验证方法: ①字符串(string) 测试基本操作 set、get、incr、decr 验证字符串的长度和内容是否正…...

以太网PHY布局布线指南
1. 简介 对于以太网布局布线遵循以下准则很重要,因为这将有助于减少信号发射,最大程度地减少噪声,确保器件作用,最大程度地减少泄漏并提高信号质量。 2. PHY设计准则 2.1 DRC错误检查 首先检查DRC规则是否设置正确,然…...
Python打卡训练营学习记录Day49
知识点回顾: 通道注意力模块复习空间注意力模块CBAM的定义 作业:尝试对今天的模型检查参数数目,并用tensorboard查看训练过程 import torch import torch.nn as nn# 定义通道注意力 class ChannelAttention(nn.Module):def __init__(self,…...

设计模式-3 行为型模式
一、观察者模式 1、定义 定义对象之间的一对多的依赖关系,这样当一个对象改变状态时,它的所有依赖项都会自动得到通知和更新。 描述复杂的流程控制 描述多个类或者对象之间怎样互相协作共同完成单个对象都无法单独度完成的任务 它涉及算法与对象间职责…...

android关于pthread的使用过程
文章目录 简介代码流程pthread使用hello_test.cppAndroid.bp 编译过程报错处理验证过程 简介 android开发经常需要使用pthread来编写代码实现相关的业务需求 代码流程 pthread使用 需要查询某个linux函数的方法使用,可以使用man 函数名 // $ man pthread_crea…...