MyBatis基本操作及SpringBoot单元测试
目录
一、什么是单元测试?
1.1 单元测试的好处
1.2 单元测试的实现步骤
1.2.1 生成单元测试类:
1.2.2 @SpringBootTest注解
1.2.3 检验方法结果:
二、利用MyBatis实现查询操作
2.1单表查询
2.2 参数占位符 #{} 和 ${}
2.2.1 ${} 字符直接替换
2.2.2 #{} 预编译处理
2.2.3 #{} 和 ${} 两者的区别
2.2.4 SQL注入问题
2.3 like查询
2.3.1 引入concat解决#{}的问题
2.3.2 当出现实体类类名与数据库字段名不相同的时候该怎么处理?
2.4 多表查询
三、利用MyBatis实现修改操作
四、利用MyBatis实现删除操作
五、利用MyBatis实现添加操作
前言:本篇出自博主的上一篇博客快速入门MyBatis,以下操作的数据库皆为上章所提及,这里就不再演示。
在介绍单元测试之前,先来看一组操作:
以下是根据所给的 ID 来查询用户名:


如果没有使用单元测试的情况下需要验证该功能的正常性,就只能通过Service调用Mapper层,再根据Controller层调用Service层:

代码实现如下:

一、什么是单元测试?
单元测试(unit testing),是指对软件中的最⼩可测试单元进⾏检查和验证的过程就叫单元测试。
Spring Boot 项⽬创建时会默认单元测试框架 spring-boot-test,⽽这个单元测试框架主要是依靠另⼀个著名的测试框架 JUnit 实现的:
打开 pom.xml 就可以看到,以下信息是 Spring Boot 项⽬创建是⾃动添加的:

1.1 单元测试的好处
- 可以⾮常简单、直观、快速的测试某⼀个功能是否正确。
- 使⽤单元测试可以帮我们在打包的时候,发现⼀些问题,因为在打包之前,所以的单元测试必须通过,否则不能打包成功。
- 使⽤单元测试,在测试功能的时候,可以不污染连接的数据库,也就是可以不对数据库进⾏任何改变的情况下,测试功能。
针对第二点:

1.2 单元测试的实现步骤
1.2.1 生成单元测试类:



按照上述步骤之后,就会生成如下代码:
1.2.2 @SpringBootTest注解
记得加上@SpringBootTest注解,随后完善测试方法的具体实现:
package com.example.demo.mapper;import com.example.demo.entity.UserEntity;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import static org.junit.jupiter.api.Assertions.*;@SpringBootTest
class UserMapperTest {@Autowiredprivate UserMapper userMapper;@Testvoid getUserById() {UserEntity user = userMapper.getUserById(1);System.out.println(user);}
}
1.2.3 检验方法结果:

如果当@Param里面的参数改为uid,其他地方不再进行修改,那么程序会报错嘛?
:会的,以下是运行结果

分析:
具体来说,@Param注解可以应用于方法的参数上,用于指定参数的名称。这个名称会与 SQL 查询语句中的${}或#{}配合使用,以匹配对应的参数。
二、利用MyBatis实现查询操作
2.1单表查询
UserMapper.xml 代码如下:

UserMapper.java 如下:
2.2 参数占位符 #{} 和 ${}
- #{} : 预编译处理。
- ${}: 字符直接替换。
预编译处理 :MyBatis 在处理#{} 时,会将SQL中的 #{} 替换为 ? 号,使用PreparedStatement的set方法来赋值。
直接替换:是将MyBatis在处理${} 时,就是把${} 替换成变量的值。
为了更好的观察两者的区别,我们需要打印MyBatis执行的SQL语句,在此之前需要在application.properties中完成以下配置:
#配置MyBatis的xml保存路径
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
#配置打印打印的日志级别(默认的日志级别是info,需要设置为debug才能显示出来)
logging.level.com.example.demo=debug
2.2.1 ${} 字符直接替换

运行以下测试方法进行检查功能是否正确,运行代码如下所示:

2.2.2 #{} 预编译处理

运行以下测试方法进行检查功能是否正确,运行代码如下所示:

2.2.3 #{} 和 ${} 两者的区别
可能观察了上面这些运行结果,并没有发现这两者之间有什么区别,这是因为之前都是使用int类型进行的传参,当使用String类型进行传参的时候,就会发送改变。
以下为 #{} 占位符模式,可以观察到程序运行正常。

接着我们使用${} 直接替换,观察运行结果,发现程序报错:

细心观察,我们发现:这是因为采用了直接替换的格式,为加任何修饰,于是程序出现了错误,就好比以下SQL代码:

这是因为采用了直接替换,没有使用引号,于是程序出现了问题,正常的sql语句应该如下:
或者可以采取这样的方式来避免刚刚的错误:
而${}所采用的直接替换的方式,容易引入SQL注入问题。 【下文会提到】
除此之外,两者还有性能的区别。
使用 #{} 时,参数值会以预编译参数的形式传递给数据库,数据库会对参数进行安全处理。这样可以提高数据库的执行效率,尤其是对于频繁执行的SQL语句,数据库可以重复使用编译好的执行计划,从而提高查询性能。
而使用 ${} 时,参数值会直接替换到SQL语句中,相当于字符串拼接。这样可能导致SQL语句的执行计划在每次执行时都需要重新编译,降低了数据库的执行效率。
因此,从性能角度考虑,推荐使用#{}来处理参数,尽量避免使用${}。特别是当参数值来自用户输入时,使用${}可能存在安全风险,同时也可能降低数据库的执行效率。
可能有的人会问:既然#{}性能比${}高,而且#{}还能防止SQL注入的问题,那么#{}存在的意义是什么?
${}存在的原因是为了一些特殊场景的需求,它提供了更大的灵活性,例如当某些场景需要根据某个数值进行排序的时候:

当使用${}的时候,程序就会正常运行无误:

小结:
总结起来,#{}是安全的参数占位符,能够防止SQL注入攻击,而${}是简单的字符串替换,需要谨慎使用以避免安全风险。在编写SQL语句时,建议使用#{}来处理参数,尽量避免使用${},特别是当参数值来自用户输入时。
ps:在MyBatis中,无论是#{}还是${},底层的实现都是PreparedStatement,而不是Statement(Statemt执行的是不带参数的SQL语句)。
2.2.4 SQL注入问题
SQL注入常见于登录的时候,以下带来示例:
观察数据库我们可以明白,用户的密码是 admin:

如果在使用${}的情况下,不输入正确密码,而利用SQL注入,便可绕过验证,获取用户信息:

如果是使用#{}则不会出现这样的问题:

这是为什么呢,下面来一组图来解释一下:

需要注意的是,1是等于符号1的,这里可以利用MySQL语句进行进一步验证:

2.3 like查询
UserMapper.xml如下所示:

UserMapper.java代码如下所示:
测试代码如下:

运行结果:(程序出现错误)

2.3.1 引入concat解决#{}的问题
可能有人觉得很奇怪,明明操作没有毛病啊,下面我们来解释一下这个原因,当使用#{}进行处理的时候,实际在Mysql中是转换成这样:

concat的介绍
- 在 MySQL 中, concat函数用于将多个字符串连接成一个字符串。它接受两个或多个参数,将它们按顺序连接在一起,并返回连接后的结果。
为了避免出现多余的单引号,我们可以使用concat对其进行拼接,以下是示例:

可以观察到,使用concat可以对这些参数进行拼接,这些参数可以是字符串常量、列名或表达式。
以下是使用concat对原先like查询修改后的结果:

观察结果,程序运行正常,且查询到的结果与数据库吻合:
2.3.2 当出现实体类类名与数据库字段名不相同的时候该怎么处理?
在实际开发的过程中,经常会出现数据库设置的字段名与我们实体类中的名字不相同的情况(需要注意:如果大小写不一致是不影响的):

由于MyBatis是一个ORM框架,如果两者不一致的话(不区分大小写的,例如updateTime就不受影响),是无法进行映射操作的。我们运行测试方法,发现实体类中的pwd属性的结果为空,进一步验证以上观点:

此时有两种解决方案,一种是使用Mysql提供的as关键字,另一种是使用resultMap。
①使用as关键字解决上述问题,如下所示:

②使用resultMap:
这里利用resultMap中的id 和result标签完成映射,拿到了pwd的值:
分析:
1.id 标签:id 标签用于指定主键列的映射关系。它定义了将查询结果中的某一列映射到目标对象的主键属性上。主键属性通常是唯一标识一个对象的属性。在 id 标签中,需要指定两个属性:
- property:指定目标对象的属性名,即主键属性名。
- column:指定查询结果中的列名,即主键列名。
2.result 标签:result 标签用于指定普通属性的映射关系。它定义了将查询结果中的某一列映射到目标对象的普通属性上。普通属性是对象中除主键属性外的其他属性。在 result 标签中,需要指定两个属性:
- property:指定目标对象的属性名,即普通属性名。
- column:指定查询结果中的列名,即列名。
2.4 多表查询
输入作者id得到文章详情信息,以下是ArticleMapper.xml实现代码:

ArticleMapper.java实现代码:
以下是ArticleInfo和ArticleInfoVo的实现:
运行测试代码,效果如下所示:

分析发现:只有ArticleInfoVo的属性,并没有打印出父类的属性。
初步判断是使用Lombok插件所导致的,让articleinfovo在使用toString方法的时候只打印自己的属性而不打印父类的属性:
接下来我们查看target目标文件夹下的字节码文件来验证:

通过观察,我们确信是因为我们使用Lombok插件所导致的这一现象。
解决方案:在ArticleInfoVo中重写toString方法(在使用Lombok插件时,如果自己重写的方法与插件所提供的方法相冲突,以自己书写的方法为主):

Ps:一定要选择重写类型,否则默认情况下重写的toString方法没有继承父类属性。
再次运行测试方法,即可打印父类属性:

三、利用MyBatis实现修改操作
UserMapper.java的代码:

UserMapper.xml的代码:

注:在MyBatis的<update>标签中,不需要设置resultType参数的原因是因为<update>标签通常用于执行更新操作(如插入、更新、删除),这些操作的返回结果通常是受影响的行数,而不是具体的结果对象。
进行测试,效果如下所示:

由于前面都是使用查询操作,并未涉及修改数据库信息等操作,所以在单元测试中未提及@Transactional注解,这里简单介绍一下:
@Transactional注解是用于声明方法或类需要进行事务管理的注解。它可以应用在方法级别或类级别上。
当@Transactional注解被应用在方法上时,它表示该方法需要在事务控制下执行。事务是一种用于保证一组操作要么全部成功执行,要么全部回滚的机制。在方法执行期间,如果发生异常,事务将回滚,否则,事务将提交。
单元测试的方法在加了该注释后,会自动的进行回滚操作,这样保证测试的方法不会污染数据库的数据:

可以打开Mysql进行进一步验证,上述代码是将用户1的密码123456修改为了1234567:

可以观察到,数据库的数据是未发生变化的。
四、利用MyBatis实现删除操作
UserMapper.java如下所示:

UserMapper.xml如下所示:
进行测试,效果如下所示:
同样的<delete>标签用于执行删除操作,通常用于删除数据库的记录,由于其不返回任何结果,因此在MyBatis中使用delete标签时,不需要设置’resultType‘参数。
Ps:resultType参数用于指定 SQL 语句执行后返回的结果类型,它通常用于查询操作,用于映射查询结果到 Java 对象或其他类型。对于删除操作来说,我们只关心删除的行数,而不需要获取具体的结果对象,因此不需要设置 resultType参数。
五、利用MyBatis实现添加操作
UserMapper.java如下所示:

UserMapper.xml如下所示:

进行单元测试:
查询Mysql进行验证,发现确实新增了一条数据:

相关文章:
MyBatis基本操作及SpringBoot单元测试
目录 一、什么是单元测试? 1.1 单元测试的好处 1.2 单元测试的实现步骤 1.2.1 生成单元测试类: 1.2.2 SpringBootTest注解 1.2.3 检验方法结果: 二、利用MyBatis实现查询操作 2.1单表查询 2.2 参数占位符 #{} 和 ${} 2.2.1 ${} 字符…...
Linux之创建进程、查看进程、进程的状态以及进程的优先级
文章目录 前言一、初识fork1.演示2.介绍3.将子进程与父进程执行的任务分离4.多进程并行 二、进程的状态1.进程的状态都有哪些?2.查看进程的状态2.运行(R)3.阻塞4.僵尸进程(Z)1.僵尸状态概念2.为什么要有僵尸状态&#…...
k8s部署rabbitmq
docker pull rabbitmq:3.9.28-management 1.部署模板 apiVersion: v1 kind: Service metadata:name: rabbitmq spec:ports:- name: amqpport: 5672targetPort: 5672- name: managementport: 15672targetPort: 15672selector:app: rabbitmq---apiVersion: apps/v1 kind: Statef…...
关于QGroundControl的软件架构的理解
首先QGC是基于QT平台开发,个人理解软件架构即为项目前后端结构,以及前后端数据交互的逻辑。下面是对QGroundControl源码的一些个人理解,写这个博客只是为了记录下来,防止时间久了忘记,过程中看了一些大佬的博客来帮助理…...
Android 文本识别:MLKIT + PreviewView
随着移动设备的普及和摄像头的高像素化,利用相机进行文本识别成为了一种流行的方式。MLKit 是 Google 提供的一款机器学习工具包,其中包含了丰富的图像和语言处理功能,包括文本识别。PreviewView 是 Android Jetpack 的一部分,它提…...
刮泥机的分类有哪些及组成部分
刮泥机的分类有哪些及组成部分 刮泥机的分类: 刮泥机主要包括:周边传动刮泥机、中心传动浓缩刮泥机。 1、中心传动浓缩刮泥机:主要由溢流装置、大梁及拦杆、进口管、传动装置、电器箱、稳流筒、主轴、浮渣耙板、刮集装置、水下轴承、小刮刀、…...
Qt编程基础 | 第六章-窗体 | 6.2、VS导入资源文件
一、VS导入资源文件 1.1、导入资源文件 步骤一: 将所有图片放到各自文件夹下,并将文件夹拷贝到资源文件(.qrc文件)的同级目录下,如下: 步骤二: 新建VS项目的时候,系统会自动建好一…...
NET框架程序设计-第4章类型基础
4.1 所有类型的基类型:System.Object CLR 要求每个类型最终都要继承自 System.Object 类型。 两种类型定义: 1)隐式继承 //隐式继承 Object class Employee{}2)显式继承 class Employee:System.Object{}System.Object 主要的公…...
Java设计模式-备忘录模式
简介 在软件开发中,设计模式是为了解决常见问题而提出的一种经过验证的解决方案。备忘录模式(Memento Pattern)是一种行为型设计模式,它允许我们在不破坏封装性的前提下,捕获和恢复对象的内部状态。 备忘录模式是一种…...
Zookeeper集群 + Kafka集群
Zookeeper 概述 Zookeeper 定义 Zookeeper是一个开源的分布式的,为分布式框架提供协调服务的Apache项目。 Zookeeper 工作机制 Zookeeper从设计模式角度来理解:是一个基于观察者模式设计的分布式服务管理框架,它负责存储和管理大家都关心的数…...
“邮件营销新趋势,这个平台让你收获颇丰!
随着各媒体平台的迅速发展,2023年大家更专注于视频营销、网红营销、直播营销等营销方式。可以见得,数字媒介手段的发展,对于营销方式也产生了巨大的影响。但是,企业在拥抱新兴的营销方式的同时,也不要忽视传统的营销方…...
Python列表推导
列表推导式 列表推导式创建列表的方式更简洁。常见的用法为,对序列或可迭代对象中的每个元素应用某种操作,用生成的结果创建新的列表;或用满足特定条件的元素创建子序列。 例如,创建平方值的列表: squares [] for …...
git使用查看分支、创建分支、合并分支
一、查看分支 查看的git命令如下: git branch 列出本地已经存在的分支,并且当前分支会用*标记 git branch -r 查看远程版本库的分支列表 git branch -a 查看所有分支列表(包括本地和远程,remotes/开头的表示远程分支)…...
vue3.0与vue2.0
一、生命周期的变化 1.vue2.响应式架构 2.vue3.0 响应式架构图 Vue3.0响应式框架在设计上,将视图渲染和数据响应式完全分离开来。将响应式核心方法effect从原有的Watcher中抽离。这样,当我们只需要监听数据响应某种逻辑回调(例如监听某个text属性的变化…...
HTML 中的常用标签用法
HTML是构建Web页面的基础语言,其中包含许多不同类型的标签。这些标签由尖括号包围,以指示浏览器如何呈现文本。下面是HTML中的一些常用标签以及它们的使用方法: 标题标签(h1-h6) 标题标签用于标识页面内容的标题&…...
【C++】指针 - 定义和使用,所占内存空间,空指针,野指针,const 修饰指针,指针和数组,指针和函数
文章目录 1. 定义和使用2. 所占内存空间3. 空指针4. 野指针5. const 修饰指针6. 指针和数组7. 指针和函数 1. 定义和使用 数据类型 * 变量名; 指针的作用是,可以通过指针间接访问内存。 内存编号是从 0 开始记录的,一般用十六进制数字表示。可以利用指…...
新规之下产业园区如何合理收费水电费用
一、政策背景 2018年3月30日,国家发改委发布《国家发展改革委关于降低一般工商业电价有关事项的通知》。明确提出进一步规范和降低电网环节收费,一是提高两部制电价的灵活性;二是全面清理规范电网企业在输配电价之外的收费项目,重…...
1011. 在 D 天内送达包裹的能力
传送带上的包裹必须在 days 天内从一个港口运送到另一个港口。 传送带上的第 i 个包裹的重量为 weights[i]。每一天,我们都会按给出重量(weights)的顺序往传送带上装载包裹。我们装载的重量不会超过船的最大运载重量。 返回能在 days 天内将…...
基于SpringBoot养老院管理系统
目录 一、项目介绍 二. 运行环境 三、项目技术 四、部署项目 五、项目运行 六、项目展示 五、项目下载 一、项目介绍 基于springboot的养老院管理系统拥有多种角色账号:管理员和用户 管理员:管理员管理、用户管理、健康管理、病例方案管理、药品…...
1.3 eBPF的工作原理初探
写在前面 上一节提到过,eBPF程序是面向BPF体系结构指令集编写的,它并不直接运行在Linux内核中,我们可以理解为它是运行在eBPF虚拟机,由eBPF虚拟机来执行eBPF字节码,就像java运行在jvm一样。 我们用一张原理图来看下eBPF程序的编译,加载,验证,钩子,映射等结点。 如上是…...
React 第五十五节 Router 中 useAsyncError的使用详解
前言 useAsyncError 是 React Router v6.4 引入的一个钩子,用于处理异步操作(如数据加载)中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误:捕获在 loader 或 action 中发生的异步错误替…...
ssc377d修改flash分区大小
1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...
SCAU期末笔记 - 数据分析与数据挖掘题库解析
这门怎么题库答案不全啊日 来简单学一下子来 一、选择题(可多选) 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘:专注于发现数据中…...
服务器硬防的应用场景都有哪些?
服务器硬防是指一种通过硬件设备层面的安全措施来防御服务器系统受到网络攻击的方式,避免服务器受到各种恶意攻击和网络威胁,那么,服务器硬防通常都会应用在哪些场景当中呢? 硬防服务器中一般会配备入侵检测系统和预防系统&#x…...
Golang dig框架与GraphQL的完美结合
将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用,可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器,能够帮助开发者更好地管理复杂的依赖关系,而 GraphQL 则是一种用于 API 的查询语言,能够提…...
【python异步多线程】异步多线程爬虫代码示例
claude生成的python多线程、异步代码示例,模拟20个网页的爬取,每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程:允许程序同时执行多个任务,提高IO密集型任务(如网络请求)的效率…...
全志A40i android7.1 调试信息打印串口由uart0改为uart3
一,概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本:2014.07; Kernel版本:Linux-3.10; 二,Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01),并让boo…...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
ABAP设计模式之---“简单设计原则(Simple Design)”
“Simple Design”(简单设计)是软件开发中的一个重要理念,倡导以最简单的方式实现软件功能,以确保代码清晰易懂、易维护,并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计,遵循“让事情保…...
MySQL 8.0 事务全面讲解
以下是一个结合两次回答的 MySQL 8.0 事务全面讲解,涵盖了事务的核心概念、操作示例、失败回滚、隔离级别、事务性 DDL 和 XA 事务等内容,并修正了查看隔离级别的命令。 MySQL 8.0 事务全面讲解 一、事务的核心概念(ACID) 事务是…...
