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> 标签下,可以看出,由于注解中是字符串拼接的方式,这种方法是非常容易出错的,而且排查错误也是有些困难的

主页 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

学习threejs,导入FBX格式骨骼绑定模型

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️THREE.FBXLoader fbx 模型加…

【腾讯云产品最佳实践】腾讯云CVM入门技术与实践:通过腾讯云快速构建云上应用

目录 前言 什么是腾讯云CVM&#xff1f; 腾讯云CVM的技术优势 基于最佳技术实践&#xff0c;使用腾讯云CVM搭建应用 1. 开通CVM实例 2. 连接CVM实例 3. 配置Web环境 4. 部署PHP应用 腾讯云CVM行业应用案例&#xff1a;电商平台的双十一攻略 1. 弹性伸缩解决高并发问题…

mongodb多表查询,五个表查询

需求是这样的&#xff0c;而数据是从mysql导入进来的&#xff0c;由于mysql不支持数组类型的数据&#xff0c;所以有很多关联表。药剂里找药物&#xff0c;需要药剂与药物的关联表&#xff0c;然后再找药物表。从药物表里再找药物与成分关联表&#xff0c;最后再找成分表。 这里…

STL中vector实现——简单易懂版

本章内容 模拟实现 vector 的部分重要功能 1.迭代器的引入1.1 之前写法1.2 STL库中的写法 2.默认成员函数2.1构造与拷贝构造2.2拷贝赋值2.3析构函数 3.增删查改功能3.1插入3.2删除 4.为什么STL中vector没有find函数&#xff1f;5.&#x1f525;&#x1f525;迭代器失效场景&am…

Springboot + vue 健身房管理系统项目部署

1、前言 ​ 许多人在拿到 Spring Boot 项目的源码后&#xff0c;不知道如何运行。我以 Spring Boot Vue 健身房管理系统的部署为例&#xff0c;详细介绍一下部署流程。大多数 Spring Boot 项目都可以通过这种方式部署&#xff0c;希望能帮助到大家。 ​ 2、项目查看 ​ 首…

NuGet如何支持HTTP源

今天是2024年11月21号&#xff0c;最近更新了VisualStudio后发现HTTP的包源已经默认禁止使用了&#xff0c;生成时会直接报错。如下图&#xff1a; 官方也明确指出了要想使用HTTP包源的解决办法&#xff0c;这里就简单总结一下。 一、全局配置 1、全局NuGet包的配置文件路径在…

SpringBoot学习记录(四)之分页查询

SpringBoot学习记录&#xff08;四&#xff09;之分页查询 一、业务需求1、基本信息2、请求参数3、相应数据 二、传统方式分页三、使用PageHelper分页插件 一、业务需求 根据条件进行员工数据的条件分页查询 1、基本信息 请求路径&#xff1a; /emps 请求方式&#xff1a; …

JavaParser如何获取方法的返回类型

使用JavaParser 如何获取一个Java类中的某个方法的返回类型呢&#xff1f; 假如有一个如下的简单的Java 类&#xff1a; /*** Copyright (C) Oscar Chen(XM):* * Date: 2024-11-21* Author: XM*/ package com.osxm.ai.sdlc.codeparse.codesample;public class MyClass {public…

2024亚太杯国际赛C题宠物预测1234问完整解题思路代码+成品参考文章

中国宠物业发展趋势及预测模型 一、问题背景与研究目标 近年来&#xff0c;中国宠物业经历了快速发展&#xff0c;特别是在城市化进程加快、人口结构变化和消费水平提升的背景下&#xff0c;宠物作为家庭成员的角色变得愈发重要。根据相关数据&#xff0c;中国宠物数量&#…

Java实现离线身份证号码OCR识别

最近公司要求做离线身份证OCR功能&#xff0c;找了一圈总算是找到了&#xff0c;在这里对文档做个整理&#xff0c;方便后来者&#xff0c;感谢码龄23年博主的分享 系统&#xff1a;Windows11&#xff0c;红旗Linux Asianux8.1 文档中Linux全root用户操作&#xff1b;需先安装…

Gradle核心概念总结

这部分内容主要根据 Gradle 官方文档整理&#xff0c;做了对应的删减&#xff0c;主要保留比较重要的部分&#xff0c;不涉及实战&#xff0c;主要是一些重要概念的介绍。 Gradle 这部分内容属于可选内容&#xff0c;可以根据自身需求决定是否学习&#xff0c;目前国内还是使用…

鸿蒙网络编程系列50-仓颉版TCP回声服务器示例

1. TCP服务端简介 TCP服务端是基于TCP协议构建的一种网络服务模式&#xff0c;它为HTTP&#xff08;超文本传输协议&#xff09;、SMTP&#xff08;简单邮件传输协议&#xff09;等高层协议的应用程序提供了可靠的底层支持。在TCP服务端中&#xff0c;服务器启动后会监听一个或…

第5-1节:SpringBoot对SpringMVC的自动配置

我的后端学习大纲 SpringBoot学习大纲 1、SpringBoot对SpringMVC自动配置概览

Emacs进阶之插入时间信息(一百六十三)

简介&#xff1a; CSDN博客专家、《Android系统多媒体进阶实战》一书作者 新书发布&#xff1a;《Android系统多媒体进阶实战》&#x1f680; 优质专栏&#xff1a; Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a; 多媒体系统工程师系列【…

嵌入式实验报告:家用计时器

实验目的和要求 1、实验目的 掌握STM32串口通信原理。学习编程实现STM32的UART通信掌握STM32中断程序设计流程。熟悉STM32固件库的基本使用。熟悉STM32定时器中断设计流程。2、实验要求 设计一个家用计时器,其功能如下: 利用串口设置计时时间,格式:XX:XX:X 例如01:59:…

【WRF理论第十二期】Registry.EM 文件详解

【WRF理论第十二期】Registry.EM 文件详解 Registry.EM 文件的作用Registry.EM 文件的结构Registry.EM 文件内容理解如何修改 Registry.EM 文件以输出特定变量WRF-Urban 修改 Registry.EM 文件以输出 UCM 相关变量1. 修改 Registry.EM 文件2. 重新编译 WRF 注意事项参考 在 WRF…

Midjourney 图生图,真人二次元保持一致性,场景多元可选择

Midjourney 拥有强大的图生图的功能&#xff0c;下面我们就来看一下&#xff0c;如何在我们的AceDataCloud网站上实现将照片切换成任意的二次元场景&#xff0c;同时保持人物的一致性。 我们可以按照如下的步骤去实现人物一致性。 下面我们来看看效果吧&#xff0c;原图如下。…

三种复制只有阅读权限的飞书网络文档的方法

大家都知道&#xff0c;飞书是一款功能强大的在线协作工具&#xff0c;可以帮助团队更高效地协作和沟通。越来越多的资料都在使用飞书文档&#xff0c;在使用飞书的过程中&#xff0c;发现很多文档没有复制权限&#xff0c;如果想要摘抄笔记&#xff0c;只能一个字一个字地敲出…

【GL003】TCP/IP 协议

目录 一、TCP/IP协议简介 二、TCP/IP协议的分层模型 2.1 OSI模型的七层框架 2.2 TCP/IP协议层&#xff08;四层&#xff09; 2.2.1 TCP/IP协议层与ISO模型 2.2.2 TCP/IP协议层的作用 三、TCP协议的报文格式 3.1 什么是报文 3.2 TCP报文 四、TCP的通信连接 4.1 TCP…

Spring WebFlux学习笔记(二)

目标 运行第一个spring webflux项目 官网操作 https://start.spring.io/ 依赖、工具 jdk 21、idea、maven 运行过程 将下载的代码直接导入到idea后运行 运行上个笔记的例子 注意 需要更改为MediaType.TEXT_EVENT_STREAM_VALUE 未完待续。。。