Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔

2年前 (2022) 程序员胖胖胖虎阿
267 0 0

文章目录

  • 前言
  • 1.MyBatis 是什么?
  • 为什么要学习 MyBatis
  • 怎么学 MyBatis?
  • 1、创建 MyBatis 项目
    • 准备工作:创建数据库 和 数据表
    • 1.1、添加 MyBatis 相关依赖
      • 1、新建一个 MyBatis 项目
      • 2、在老项目中引入 MyBatis 相关依赖
    • 1.2、配置数据库连接字符串
    • 1.3、配置 MyBatis 保存的 xml 的目录
  • 使用 MyBatis 的操作模式 操作数据库
    • MyBatis 的操作模式
    • 第⼀个MyBatis查询:实现一个根据用户id来查询用户信息的操作
      • 1、定义接口
      • 2、创建 xml,实现上面的接口
      • 运行结果展示
      • 注意!有的人可能会在这一步出现问题
  • MyBatis 执行过程
  • Spring Boot的单元测试 - 穿插内容
    • 1.什么是单元测试?
  • 2、单元测试有哪些好处?
  • 3.Spring Boot 单元测试使用
    • 单元测试的实现步骤
      • 1.⽣成单元测试类
      • 2 添加单元测试代码
        • 1.添加 Spring Boot 框架测试注解:@SpringBootTest
        • 2.添加单元测试业务逻辑
      • 3、开启测试
      • 4、简单的断⾔说明
  • 拓展:“小鸟” - 插件 MyBatisX
  • 增、删、该操作
    • MyBatis 修改操作
  • 在不修改数据库中数据的情况下,完成单元测试 - 穿插
    • MyBatis 删除操作
      • 1、在 mapper 中的 UserInfoMapper 接口中定义 方法
      • 2、在xml文件中 编写 SQL语句 的相关代码。
      • 单元测试 - 验证功能效果
    • MyBatis 插入操作
      • 先实现最简单 的 插入操作
        • 1、在 mapper 中的 UserInfoMapper 接口中定义 方法
        • 2、在xml文件中 编写 SQL语句 的相关代码。
        • 3、进行单元测试
      • 上面最简单的实现方式,下面我们来看难一点的实现。
        • 先来看第二步:在xml文件中 编写 SQL语句 的相关代码。
        • 再来看一步:在 mapper 中的 UserInfoMapper 接口中定义 方法。
        • 我们测试一下。
  • 查询操作
    • 单表查询
    • 参数占位符 #{} 和 ${} 的区别
    • ${} 的经典问题:SQL注入问题
    • like 查询 - 特殊情况
    • #{} 和 ${} 的区别总结
    • 多表查询
      • 前置知识
        • 返回类型:resultType
        • 返回字典映射: resultMap
          • resultType && resultMap 的区别
    • 有了 resultMap 和 resultType 的基础,我们下面就可以真正开始进行多表查询的操作了。
      • MyBatis 多表查询:一对一关系
      • MyBatis 多表查询:一对多关系
  • 复杂情况:动态SQL使⽤
    • 动态标签 - < if >标签
    • 动态标签 - < trim >标签
    • 动态标签 - < where >标签
    • 动态标签 - < set >标签
    • 动态标签 - < foreach >标签

前言

经过前⾯的学习咱们 Spring 系列的基本操作已经实现的差不多了,接下来,咱们来学习更重要的知识,将前端传递的数据存储起来,或者查询数据库⾥⾯的数据。


1.MyBatis 是什么?

MyBatis 是⼀款优秀的持久层框架,它⽀持⾃定义 SQL、存储过程以及⾼级映射。
MyBatis 去除了⼏乎所有的 JDBC 代码以及设置参数和获取结果集的⼯作。
MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接⼝和 Java POJO(Plain Old Java Objects,普通⽼式 Java 对象)为数据库中的记录。
简单来说 MyBatis 是更简单完成程序和数据库交互的⼯具,也就是更简单的操作和读取数据库⼯具。
Mybatis官⽹

针对 MyBatis 是⼀款优秀的持久层框架 进行分析和补充。

MyBatis,也是一个 ORM 框架。
ORM(Object Relational Mapping),即对象关系映射。
在⾯向对象编程语⾔中,将关系型数据库中的数据与对象建⽴起映射关系,进⽽⾃动的完成数据与对象的互相转换:
1、 将输⼊数据(即传⼊对象)+SQL 映射成原⽣ SQL
2、将结果集映射为返回对象,即输出对象

ORM 把数据库映射为对象:
数据库表(table)>>> 类(class)
记录(record,⾏数据)>>> 对象(object)
字段(field) >>> 对象的属性(attribute)
⼀般的 ORM 框架,会将数据库模型的每张表都映射为⼀个 Java 类。
也就是说使⽤ MyBatis 可以像操作对象⼀样来操作数据库中的表,可以实现对象和数据库表之间的转换。

即:MyBatis 可以称为是一座“桥梁”。
它的作用就是:将 数据库 和 程序,映射起来。
映射起来之后,就可以做一系列的操作了。
比如:
查询,由于我们将 数据库 与 程序映射起来了,直接就把整个数据表映射到一个类里。
查询的结果,就是一个集合,存储的是类的对象(插叙到的一行行记录),那数据也就查询到了。

保存,还是一样的,我们已经将 数据库 与 程序 映射起来了,把表里面的数据,搞到对象里面了。那我们调用对象的某个方法,是不是就可以直接对象的成员直接保存到 MySQL 里面了呢!

MySQL 和 MyBatis,是不一样的。
MySQL,提供了一个 数据存取(数据管理) 的软件。
而 MyBatis 是一个“中间桥梁”,用于连接 程序 和 数据库,建立关系映射的,进行 数据操作 的中间层(持久层)。

总的来说:
MyBatis 的定义:MyBatis 是一个优秀的 ORM(对象关系映射)持久层框架。
其作用:实现 程序 和 数据库 之间的数据交互。

PS:
ORM 框架,并不是只有 MyBatis 一家!
只是说 MyBatis 在国内使用的标胶广泛,大概占据中国市场的90%以上。
国外用的最多的是Hibernate。

至于其中的原因,MyBatis 和 Hibernate 之间的区别等等。。。
包括 在 Hibernate 之后,又有一个 Spring JPA,与其又有和区别?
等 MyBatis 讲完之后,再给你们讲。
(前提是我还记得,没写你们就自己百度吧)

MyBatis ⽀持⾃定义 SQL

自定义SQL,包含的范围就很广了。
数据库中的 “CURD(增删查改)”语句,都是可以实现的。
自定义SQL嘛,我们想写什么SQL,就写什么SQL。
由此,不难看出 MyBatis 一个 最大的特点:灵活!
这也是为什么 MyBatis 在国内能够流行的重要原因!!!

其背后的原因,就是国内用户的需求实在是太奇葩了,参差不齐!!

举个生活周围的例子:
几乎所有大学,都会有 计算机专业。
但是!有哪个学校是有 关于 互联网产品的专业?没有!!!
大学不会教,外面培训班又少。
大部分都是 考研的培训班,再其次就是关于 计算机 的培训。
计算机的发展,还是比较成熟的。
但是呢!关于 产品经理 和 产品 的 培训,基本上做这个培训的机构。
反正我是没见过。
原因,就是因为它的市场占有率不高,看不到利益的苗头。
自然也就不会与人去做这件事。
这么说吧:在有些公司,根本就没有产品经理,这个职位。

因此,我们以后所遇到的产品经理,大部分都是只长了一张嘴,会提需要,但是不知道这个需求实现起来,有多复杂!
因为没有培训过,再加上现在的产品经理,大部分都是不懂技术的,
因此,他们提出的需求,千奇百怪。
于是就有了 手机壳的那个段子,完全就是扯犊子。
一个软件,一个硬件,八竿子打不着的两个东西。

因此,MyBatis 灵活性 的作用,就体现出来了。
就是为了 适应 各种需求。

国外,对于 技术 和 产品 已经流程化了,都是经过培训的。
毕竟计算机,外国才是起源地嘛,发展的快,也是很容易理解的。
发展倒极致,就是稳定了。

存储过程

我之前将的 SQL,不管是多玛复杂的SQL,我们都是一行SQL解决。
因为 SQL中,并没有特别复杂的业务,像 for循环,if判断。。。这些都没有。
就是一个 很平滑,单一维度 的 SQL 操作,这是针对于 普通 SQL。

但是!存储过程,是SQL中的方法,它是由一大堆 SQL 的组成的。
这个组成里面,它是有循环的,判断的,分支的,有变量传递的。
这个就叫做存储过程,也就是将 SQL 方法化。
反过来理解:
SQL 方法化 的过程所产生的东西,就叫做存储的过程 。

以后,我们工作了之后,别人给我们一存储过程,里面就是密密麻麻的SQL语句。
还有 if 判断,for循环,设置变量的值…
然后,入参,出参,反正就是很大一串的SQL语句。
少则 几十行,多则 上千行。

那为什么我们程序员不会这个存储过程呢?
这其实和 存储过程 的 特性相关的。
虽然 存储过程 能够实现复杂的业务,但是!这只是 将业务 从 程序方 移动到了 SQL 方。
这种情况下,公司对 程序员的要求 就会非常高!!!

试想一下:公司给一个 存储过程,一个几百行的 SQL语句,你估计眼睛都花了。
你能看得懂,才有鬼。。。

但是 总得有人来做吧,这就诞生一个特殊的职业 DBA。
DBA:Database Administrator —— 数据库管理员,又称数据库开发工程师 。
他的SQL是很牛的,他不用写程序的。
每天都是在捣鼓 SQL 语句的,只玩数据库的,玩数据的。

对于 大部分程序员来说:根本就搞不懂 存储过程 ,这么复杂的东西!
而且,存储过程中有一个特别特别难用的东西!
我们在敲一个很复杂的 java 代码的时候,可以通过 debug 可以一步一步去调试,进入类,方法,属性中,来观察运行时的状态。从而能够帮助我们排查程序中存在的错误。也及时 程序 是能够调试的。

但是 存储过程 是不能调式的。
也就是说:一个存储过程,有这几百行的SQL语句,要我们去写。
而且,出现了错误,只能通过肉眼去排查错误。【不支持调试】
我敢说:百分之90的程序员,都做不到这一点。
这是属于 真正的天才,才能胜任的工作。

但是!也请放心!
公司也不可能让我们程序员去做这件事的。
一般都是外聘的专业人员来操作的。
存储过程,用的最多的就是 医院。
医院需要对每天,每月,每年,进行数据(收入,考勤。。。)汇总(各种报表)。
总之涉及到数据,非常多。
这就需要从各个数据表中 抽取数据。
此时 简单的SQL语句,就搞不定了。
这个时候,才会用到存储过程。

由于存储过程 的难度,非常大!
对人要求很高,随之而来的就是高额的工资。
大概是我们程序员工资的 1.5 倍以上。
即:招一个程序员约定1万工资,那 DBA 的工资,至少是 1w5 以上,要不然你找不到人的。

高级映射

高级映射,除了可以实现 数据表 和 程序里面的对象 映射之外,还可以实现 一对一的多表映射 和 一对多的映射。

比如: CSDN
每一篇博文,都有它的作者。
博文 与 作者 的关系,就是一对一的关系。
此时,使用 MyBatis 就可以在进行查询的时候,进行 连表查询,并且将 查询到的数据结果,映射到 我的某一个 文章对象里面。

再来,一个作者,他可以发很多篇博文。
站在这个角度,作者 与 他发布的博文,成 一对多的关系。
这种情况,使用 MyBatis 也可以实现这个任务。
MyBatis 可以直接去查询数据库里面的数据,然后,将这些 数据 赋值 给 UserInfo。
这个 UserInfo 里面是有一个属性的(list article - 文章列表)。
就是用来记录这个作者创作的作品。

MyBatis 去除了⼏乎所有的 JDBC 代码以及设置参数和获取结果集的⼯作。

以往,我们在进行 JDBC 编程的时候,需要获取数据源对象(DataSource),然后设置三个属性 URL(数据源路径),用户名(默认是 root),密码(MySQL的登录密码)。
在设置完这三项之后,然后才能与数据连接,进行一系列的操作。

另外在获取 结果集的时候,我们需要使用到一个 类(ResultSet)的对象,来接收数据库返回的结果集,还需要通过 迭代器的方式,才能进行后续的打印。

使用了 MyBatis ,上鞋这些就都不需要了。
它会自动帮我们实现映射,
我们只需要写 SQL 语句(不要写错),写完之后,它会把这个 SQL语句 执行,将 查询到的结果(表数据),直接全部映射到对象里面。
也就是说: JDBC 的前置操作的代码,我们一行都不需要写。
MyBatis 直接全包了,你直接用就行。
特别爽!

MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接⼝和 Java POJO(Plain Old Java Objects,普通⽼式 Java 对象)为数据库中的记录。

MyBatis 在实现的时候,它有两种实现方式:
1、通过 xml 来实现数据的操作,数据操作支持所有的类型。
这是在 MyBatis 3.1 版本之前,主流的一个操作方式。
使用 xml 的形式,来实现 对数据库进行 “CURD” 操作。

但是呢!在3.1 版本之后,MyBatis 有了新的选择。
也就是 第二种方式(注解方式)。
但是目前主流还是使用的 xml 形式。
为什么呢?
我们不说 MyBatis 使用起来,非常灵活!
灵活是需要付出代价的!
代价,就是我们要去写 SQL语句。
你试想一下:我们在注解中写一个 SQL语句,是不是很别扭?
代码没多少,注解倒是非常长。
所以,MyBatis 在 3.1版本之后, 提供了 注解 这个新的操作数据方式。
它也没有用,因为不好用。
注解的方式,用来实现简单 SQL 操作,还是可以的。
但是!一旦SQL语句复杂了一点,你在使用的时候,就会特别别扭。
注解 比 代码,还有长。。。
而 xml 写SQL语句比较灵活,也不会感觉到别扭,好用!

所以,本文也是 基于 xml 来实现 对数据库 进行操作的。


为什么要学习 MyBatis

对于后端开发来说,程序是由以下两个重要的部分组成的

1、后端程序
2、数据库
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔

⽽这两个重要的组成部分要通讯,就要依靠数据库连接⼯具,那数据库连接⼯具有哪些?
⽐如之前我们学习的 JDBC,还有今天我们将要介绍的 MyBatis,那已经有了 JDBC 了,为什么还要学习 MyBatis?

这是因为 JDBC 的操作太繁琐了,我们回顾⼀下 JDBC 的操作流程:
1、 创建数据库连接池 DataSource
2、 通过 DataSource 获取数据库连接 Connection
3、 编写要执⾏带 ? 占位符的 SQL 语句
4、 通过 Connection 及 SQL 创建操作命令对象 Statement
5、 替换占位符:指定要替换的数据库字段类型,占位符索引及要替换的值
6、 使⽤ Statement 执⾏ SQL 语句
7、 查询操作:返回结果集 ResultSet,更新操作:返回更新的数量
8、 处理结果集
9、 释放资源
实例代码 - insert 操作 - 截取自 我自己的文章内容。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
从上述代码和操作流程可以看出,对于 JDBC 来说,整个操作⾮常的繁琐,我们不但要拼接每⼀个参数,⽽且还要按照模板代码的⽅式,⼀步步的操作数据库,并且在每次操作完,还要⼿动关闭连接等,⽽所有的这些操作步骤都需要在每个⽅法中重复书写。于是我们就想,那有没有⼀种⽅法,可以更简单、更⽅便的操作数据库呢?
答案是肯定的,这就是我们要学习 MyBatis 的真正原因,它可以帮助我们更⽅便、更快速的操作数据 库。
之前使用 jdbc 需要进行九步操作,现在使用了 MyBatis,就写一个方法,一个 SQL就搞定了。 像什么手动释放资源,处理结果集,都不用我们去做了。MyBatis “保姆级” 助手。


怎么学 MyBatis?

本文重点分为两个部分:
1、配置 MyBatis 开发环境(创建一个 SSM 项目)
2、使用 MyBatis 模式和语法 操作数据库


1、创建 MyBatis 项目

准备工作:创建数据库 和 数据表

下面我们会创建一个关于 博客 的 数据库 mycnblog
并且在 mycnblog 数据库中创建三张表。
代码给你们了,戴氏!里面暗藏 “杀机”。
会导致 产生一个 BUG,不然有解决的方法,
先就这么弄。

不过有一点需要注意!
在 文章表中的 content字段,它的类型是 text。
text 能够表示文本内容有限,在实际情况中,一般是使用 longtext。
另外, createtime 字段,我们使用了一个 now 方法,这个 now 方法,需要 MySQL 的版本,在5.5以上。
不过应该问题不大,毕竟我们 与 jdk1.8 配套的数据库版本,是 5.7版本的。

你仔细看下面创建 SQL,无论是创建 数据库,还是数据表,我都设置了 字符集为 utf8。
确保一定是支持中文的。
这样做的好处,就是:哪怕你们没有修改数据库的配置,创建出来的表,仍是是支持中文的。

还有一个小细节:在添加一个用户信息的SQL语句中,我们的密码是明文存储的。
这样去写,势必会遭到 面试官的吐槽。
即使你使用了 md5 进行加密,其实也没有多大作用。
如果只是一个 md5,其实我们可以通过 “彩虹表” 进行穷举,来获取你的密码。(注意!这不是解密,是穷举你密码的可能性)
有人肯恩就会想法:
密码 在一次 md5 的情况下,能够通过 彩虹 表 来穷举 获取密码。
那我们 对 密码 md5 两次,甚至更多呢?
那 彩虹表是不是就破解不了了??
你都能想得到,别人黑客就想不到吗??
别说两次,就算你 3次,4次,甚至是五次,人家 应对方法的。
而且,无论你md5加密几次,其实就是累加的效果,对于黑客来说无非就是多 穷举几次而已。
最靠谱的方式,就是有随机数的,有变量的。
这样做更靠谱!
因为是随机生成的嘛,没有规律可言!
想要获取密码,就非常难!
几乎是不可能的。

-- 创建数据库
drop database if exists mycnblog;
create database mycnblog DEFAULT CHARACTER SET utf8;
-- 使⽤数据数据
use mycnblog;
-- 创建表[⽤户表]
drop table if exists userinfo;
create table userinfo(
id int primary key auto_increment,
username varchar(100) not null,
password varchar(32) not null,
photo varchar(500) default '',
createtime datetime default now(),
updatetime datetime default now(),
`state` int default 1
);
-- 创建⽂章表
drop table if exists articleinfo;
create table articleinfo(
id int primary key auto_increment,
title varchar(100) not null,
content text not null,
createtime datetime default now(),
updatetime datetime default now(),
uid int not null,
rcount int not null default 1,
`state` int default 1
);
-- 创建视频表
drop table if exists videoinfo;
create table videoinfo(
vid int primary key,
`title` varchar(250),
`url` varchar(1000),
createtime datetime default now(),
updatetime datetime default now(),
uid int
);
-- 添加⼀个⽤户信息
INSERT INTO `mycnblog`.`userinfo` (`id`, `username`, `password`, `photo`,
`createtime`, `updatetime`, `state`) VALUES
(1, 'admin', 'admin', '', '2021-12-06 17:10:48', '2021-12-06 17:10:48',
1);
-- ⽂章添加测试数据
insert into articleinfo(title,content,uid)
values('Java','Java正⽂',1);
-- 添加视频
insert into videoinfo(vid,title,url,uid) values(1,'java
title','http://www.baidu.com',1);

打开你们本地的MySQL,将上述SQL,复制粘贴到 MySQL中。
PS: 建议先拷贝到 一个文本文件中,然后再拷贝到 MySQL中。
因为你如果直接拷贝上面的内容,SQL就会挤成一坨。
也就是说不带有格式的。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
你把上面代码先放在笔记本中,再粘贴到MySQL中。
不但没有挤在一起,而且SQL全部执行了,只有最后一句SQL没有执行。
直接回车,就全部OK了。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
我们下面来看看数据,有没有插入成功
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔


1.1、添加 MyBatis 相关依赖

眼尖的朋友,会发现我使用了 “相关” ,这个形容词。
没错,我们要想使用 MyBatis,光引入 MyBatis 依赖,是不够的。
我们需要引入一个依赖(关于数据库的)。

这里就会涉及到两个场景:
1、项目创建的时候,引入 MyBatis 相关依赖
2、老项目,添加 MyBatis


1、新建一个 MyBatis 项目


Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
然后,就是将 无用的文件,删除掉。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔


2、在老项目中引入 MyBatis 相关依赖

这个讲了很多次,使用 Edit Starter 插件 添加。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔


1.2、配置数据库连接字符串

不要立即启动项目!!!!
如果立即启动项目,就会报错。
指针对 社区版 idea,专业版会帮助我们自动生成关于 数据库的 配置信息。

社区版:
这是因为 没有配置 关于 数据库的数据源的 URL
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔

专业版:
直接就给我们配置好了。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔

现在我把的配置文件删除掉,重新创建一个配置文件,与社区版保持一致,我们来重新编写配置信息。

我们需要配置 4 项:
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
此时,我们再启动项目,就不会报错了。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔


1.3、配置 MyBatis 保存的 xml 的目录

我们前面说过 MyBatis 有两种 操作方法:
1、使用 xml 的形式 实现呢
2、 注解(MyBatis 3.1版本之后,开始提供)
但是!
注解的方式,并不好用。
因此,我们主要还是关注 xml 形式,是如何操作 数据库。

思考一下:
xml 是一个 资源文件,对不对?
那么,xml 文件,还是放在 resource 目录下。
还有一个问题:
放在 resource 哪个 目录底下。

需要注意的是:一般是不会将其放在 resource 根目录底下,与配置文件处于同一级目录。
因为到时候,配置文件,非常多!
此时,你放进去,不就是添乱嘛!!!

通常我们都是在resource 目录下,创建一个子目录,用来存放的。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔


使用 MyBatis 的操作模式 操作数据库

MyBatis 的操作模式

Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔

MyBatis 的 操作模式,包含两个部分:
1、Interface(方法定义)
2、xxx.xml

注意!这里是接口,不是类。
这个interface 中 会加一个注解,它是来自于mybatis里面的注解 Mapper。
加了这个注解之后,表示我们当前的这个 mybatis 类 所的事情,就是 实现 对象的映射。
这个时候,帮我们的方法名字 定义到 interface 里面。
我们 interface 里面的方法,是不能有实现的,是一个抽象方法。

思考一下:
假设,我们要根据用户ID 去查询一个用户信息。
那我们定义一个方法名有什么用?又没有具体实现,怎么可能会实现查询功能??
答案显而易见是不行的!!!
'这个时候,我们就需要另一个东西:xml 文件
mybatis 有点奇怪,感觉就像是 “怕脑门” 想出来的方法 。
就是 要想操作数据库,必须得有两个文件。
第一个,它是Java中的 一个普通接口,在接口中,定义方法名称。
定义方法名称之后,就该实现方法了。
但是接口中方法不能有实体,需要通过一个注解来进行映射方法,映射一个 xml 文件中。
当然,在xml文件中,也需要表明它是那个方法的映射。
这就是第二个文件 xml。
根据我们前面讲的映射:就是 把业务代码 转换成 SQL 语句。
也就是说我们 定义在接口中的方法,是为了声明该方法 对数据 进行 何种操作。
具体的实现 已经通过某种映射关系,映射到 xml 文件中,
我们需要在 xml 文件中,编写 对应功能的 SQL 语句,就可以了。

有的人可能会疑问:
我们可不可以将 SQL 语句 写在代码中,就像 jdbc 编程 一样。
答案:可以,但是!SQL 只能是 String 类型的,如果SQL写错,它是不会报错的。
这就很容易翻车。
所以,mybatis 的设计师 思考一会,这样的话要不直接把 SQL 写在 xml 里面?
于是一拍大腿,就这么决定了。
在xml里面,只需要配置 我们要是实现的 interface 是谁,我要实现的 SQL 是谁。
OK ,这样一配置就完了。
这两个合在一起,最终生成 mybatis 中能执行的SQL。
通过这个SQL语句,去操作数据库。
将 操作数据库得到的结果,返回给 服务层。
服务层,再返回给 controller(控制层)。
控制层,再把结果交给用户。
这样 就完成了 一次交互。

所以,我们讲 mybatis 的 操作模式,就是在讲下图中的两个文件。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
这两个配合起来,就能生成 数据库可以执行的SQL语句,并且执行 SQL,操作数据库。
并将 结果 映射到程序的对象中。


第⼀个MyBatis查询:实现一个根据用户id来查询用户信息的操作

我们要实现一个 根据 用户id 来查询用户信息的操作。
前面我们在准备工作中,就是已经给 userinfo 表中插入了一个数据。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
下面,我们就来完成这样的一个功能。


1、定义接口

Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔


2、创建 xml,实现上面的接口

xml 文件,不能随便创建。
我们在配置文件中,已经指定了 xml 文件存储路径。
并且,命名规则也指定了。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
我们必须按照规则来。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
至于 xml 文件的配置内容,直接把下面的内容,拷贝到里面去。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="">
</mapper>

相信很多朋友,都会发现 mapper标签中的 namespace 属性,缺少value值。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
namespace 的值,是需要我们手动去填写的。
填写的内容:是需要时实现的接口位置 >> 包名 + 接口名称。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
接下来,就是在 xml 文件中,实现 UserInfoMapper 接口 中的 方法。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
关于获取方法参数,使用的 ${} 和 #{} 的区别,后面会详细讲。
&enso;
好,关于 MyBatis 的操作,就都完成了。
下面,我们就可以来验收成果了。


运行结果展示

我们在 service 包底下创建一个 UserInfoService 类。
在里面 写一个方法, 去 调用 UserInfoMapper 中的方法。
然后,在 controller 包下,也去创建这样的一个类,去调用 UserInfoService 中的方法。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
此时,我们将项目启动起来。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔


注意!有的人可能会在这一步出现问题

Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔

我被坑的头皮发麻。
第一次遇见版本不匹配,引起的错误。
看异常信息检查不出错误。
出现这个问题的原因是:我们添加的 MyBatis r的版本太高了,而我们使用的 Spring Boot 版本太低了(阿里云提供的)。导致的版本不兼容。
因此 URL 不能像正常情况下,那样去配置。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
应该这样去配置
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
更简单解决方法的是:使用官方 提供的Spring Boot 框架,选择次新的。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
我使用的是 阿里云,所以 版本很低,我是用的版本是 2.3.7。
所以,才会出现上述情况。


MyBatis 执行过程

Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔

在这里,我在补充一点。
我们在写 mybatis 代码的时候,无非就是两个操作:1、创建接口,定义方法;2、在写满了文件中编写SQL。
整个 mybatis 操作 完成了。

这里有一个比较麻烦的地方:
就是在我们写完功能之后,我们要进行测试的话,成本很高!

mapper的内容写完,我们还需要写 service,写 controller,还没完!
我们还需要打开 浏览器方法(或者利用postman来模拟访问)。
虽然整个流程,非常有序,一环接一环。
但是!这会让人感觉很拖!
给人的感觉就是:代码被强行 “ 拉长 ” 了。

那有没有什么方法,能让我们写 mapper 内容之后,直接可以进行测试呢?
这就是下面,我要讲的东西:Spring Boot的单元测试


Spring Boot的单元测试 - 穿插内容

讲这个,是为了后面讲解 Mybatis 的内容。

1.什么是单元测试?

单元测试(unit testing),是指对软件(项目)中的最⼩可测试单元进⾏检查和验证的过程就叫单元测试。

单元测试是开发者编写的⼀⼩段代码,⽤于检验被测代码的⼀个很⼩的、很明确的(代码)功能是否正确。
执⾏单元测试就是为了证明某段代码的执⾏结果是否符合我们的预期。如果测试结果符合我们的预期,称之为测试通过,否则就是测试未通过(或者叫测试失败)。

最小可测试单元:方法
每一个方法都代表一个相应的功能。
那我们测试的最小单元,对于 Spring Boot 来说,就是一个方法。
OK,你写一个方法,我测一个方法。
测试方法,就是测试一个单元。
方法,是不可以再被分割的!
你不可能说: 我们去测试 方法中的某一段程序,或者说测试某一个属性。
因为,这叫做 调试。
调试 和 测试单元,是两码事!!!!

但是!有的朋友可能会说:
方法中,经常会调用另一个方法,来“辅助”自身的运行。
那这种怎么说?
如果方法中还有方法,这个时候,单元测试就需要分为多个了。
首先是这个方法中所有依赖的方法,我们可以对这里面所有的方法,做一个单元测试。
最后,再回到本身,对本身的方法进行单元测试。


2、单元测试有哪些好处?

1、单元测试不用启动 Tomcat

对于这一点,不好说。这个说法,要分角度来看。
从自身的角度来看:我们确实没有启动 Tomcat,但是 Spring Boot 自动启动了 Tomcat。
因为 Spring Boot 内置了 Tomcat。
其实 Spring Boot 除了支持 Tomcat,还支持其它的Web容器。

2、如果中途修改了代码,在项目打包的时候会发现错误,因为打包的时候会自动执行单元测试,单元测试错误就会发现。
这一点最重要!
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
3、单元测试,最大的一个好处 :
让我们非常方便,非常直观,也非常直接的,在写完一个功能之后,立马就能知道这个功能是否是正确的!
这也是我们学习单元测试的初衷,就是为了降低 测试 mybatis 功能的成本。

4、不使用单元测试的时候,它是会 “ 污染 ” 本地的数据库的!
就是比如说:
我们实现了一个 添加 功能,我们要去测这个添加功能。
这就需要正儿八经的去进行添加操作的。
这就会对本地数据库中的数据,发送变动。
这就是 “污染”!

然而,如果使用了 单元测试 来测试功能,.
测试单元,就能保证在 测试完功能之后,对于数据库中的数据没有任何影响!

这个牵扯到了 事务的回滚机制。
这是 我在讲 MySQL是如何保证事务的原子性 的 时候,提到过的 事务回滚机制。
有兴趣,就自己去看,目录给你标出来了。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
单元测试,也是通过利用 数据库的回滚机制 来 避免对 数据产生 “ 污染 ” 。
也就是说:我们在执行单元测试之前,MySql 为其创建了一个事务。
【MySQL 默认情况下,是处于开启事务的状态,也就说开启了事务的自动提交】
然后,开始执行单元测试,对数据库中的数据进行操作。
在验证完 单元的功能没有问题之后,就会进行事务的回滚操作。
还原到 还没有测试的情况。【将数据还原到初始状态】


3.Spring Boot 单元测试使用

Spring Boot 项⽬创建时会默认单元测试框架 spring-boot-starter-test,⽽这个单元测试框架主要是依靠另⼀个著名的测试框架 JUnit 实现的,打开 pom.xml 就可以看到,以下信息是 Spring Boot 项⽬创建是⾃动添加的:
PS:我用来演示效果的项目,是我一步一步带着大家创建的。
所以,作假含量极低。我可没有偷偷添加这个依赖!
都是 Spring Boot 干的!!!雨我无瓜!
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
这里有一个细节:
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔


&enspp;

单元测试的实现步骤

1.⽣成单元测试类

Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔

这个时候,此⽅法是不能调⽤到任何单元测试的⽅法的.
此类只⽣成了单元测试的框架类,具体的业务代码要⾃⼰填充。


2 添加单元测试代码

1.添加 Spring Boot 框架测试注解:@SpringBootTest

至于为什么还需要加一个 测试注解,是因为 我们生成测试类,是一个普通的类。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
但是!测试接口,它是一个普通的接口吗?
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
因此,我们需要在测试类上 声明它要测试的方法,是运行在 Spring Boot 容器当中的。
所以,@SpringBootTest 注解,就是起着这样的一个声明作用。
表示当前这个类中 测试方法(测试单元) 是运行在 Spring Boot 中的。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔


2.添加单元测试业务逻辑

Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔


3、开启测试

Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔

虽然,从打印的结果来看,功能是没有的。
而且,左边那几个 绿色勾勾,代表测试通过。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
但是! 我们是使用 的 sout 语句,来对查询结果进行输出。
这并不是 我们 单元测试的效果!!!!
单元测试的效果应该是 单元测试中的断言!!!

断言:
就是说:以什么为标准,
通过这个标准来衡量 测试是否通过。
比如,如果 断言的结果为 true,就表示 单元测试 通过了。
反之,断言的结果为 false,就表示 单元测试 不通过。
并且!断言之后的代码,是不再执行的!!!

所以。还没有完。
单元测试,还有一部分是关于 断言 的 内容。
我们是需要知道的。


4、简单的断⾔说明

由 Assertions 类提供的,注意!这个类是 JUnit 提供的
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
下面,来跟着我看一下,如何使用断言,来判断单元测试是否通过!
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
这就是断言的魅力,一旦出错。
如果测试失败,它的报错信息会非常非常的详细!

后面,我们实现 增,删,该2的时候,就不需要 浏览器 和 postman了。
直接使用断言,来测试即可。

这里做一个小拓展:
为什么有些人的 科学版 idea, 在进行 与 @Mapper 相关的属性注入,使用 @Autowired 来注入,为什么会报错?
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔


拓展:“小鸟” - 插件 MyBatisX

它能够帮助我们快速切换 接口 和 xml。
让我们操作 MyBatus 更舒服。
而且,还能帮我们自动去生成代码。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔


增、删、该操作

接下来,我们来实现⼀下⽤户的增加、删除和修改的操作,对应使⽤ MyBatis 的标签如下:
< insert >标签:插⼊语句
< update >标签:修改语句
< delete >标签:删除语句

接口的定义,还是一样的写法。
写法的差异,主要就体现在 xml 中 的 自定义 SQL 语句 上。


MyBatis 修改操作

我们先给 userinfo表插入一条记录。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
下面,实现 改操作。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
下面我们来拓展几个细节:
1、方法参数 使用 @Param 注解之后,原先参数的名称不能再使用。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
得出结论:
@Param 注解 和 @RequestParam 注解,起着相同的作用。
方法 参数重命名。一旦重命名之后,原来的名称是不能使用的。

只不过 @Param 注解中的参数是 重命名后的结果。
而 @RequestParam 注解中的参数 是参数原名,方法参数是 重命名的结果。

于此同时,我们又发现了一个问题。
当我们测试完之后,我发现此时数据中的数据被 “污染”了!
前面不是说:单元测试不会污染 数据库中的数据嘛?
下面,我们就来实现这个功能。
【免得你们认为我是个老六,说我忽悠你们】


在不修改数据库中数据的情况下,完成单元测试 - 穿插

方法很贱简单,在测试的单元上,加上一个注解@Transactional,就行了。
@Transactional:就是事务的意思。
其作用就是在开始执行测试之前,开启一个事务。
这个事务完成的任务,就是我们定义的是SQL操作。
在 操作完成之后,也就是测试完成之后,该事务会进行一个 “ 回滚 ” 操作。
原来的数据是什么样子的,现在还是什么样子的。

就是说:事务执行的期间确实 “ 污染 ” 了 数据库中的数据,
但是!在事务执行完成之后,事务会通过 “ 回滚 ” 操作,将被 操作的数据还原到未修改的状态。

这里需要注意一点:
@Transactional 注解,原本只是提供一个 自动提交事务的功能。
并不会提供 自动 “ 回滚 ” 功能。
只是说 该 单元测试 所处于的 测试类,是被 @SpringBootTest 注解 修饰的。
等于就是说:“强行” 给 @Transactional “ 加班 ”!!!
无论 单元测试是否成功,都会进行一个 事务的 “ 回滚 ”,将数据库的数据还原!
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
我没有晃点你们吧?
只需要在 测试的单元上,再加上一个 @Transactional 注解,就可以在 “ 不污染 ” 数据库中数据的前提下,完成单元测试。

当然,这个操作的大前提:
是在 测试的单元所在的测试类,是被 @SpringBootTest 注解所修饰的。


MyBatis 删除操作

有了 修改操作,删除操作实现起来,就简单很多了。


1、在 mapper 中的 UserInfoMapper 接口中定义 方法

Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔


2、在xml文件中 编写 SQL语句 的相关代码。

注意!只有 select 标签,特殊一点(需要指定返回类型)。
delete 标签 和 update 标签都是一样的。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔


单元测试 - 验证功能效果

Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔

有了 修改 和 查询操作的基础,这个删除操作,就跟玩一样。
很快就能搞定!


MyBatis 插入操作

这个插入操作,相比前面 “ 删查改 ” 操作,要复杂一些。
所以,把它放在最后来讲。
操作还是一样的,只是插入的值,是一个对象,在 编写 SQL的时候,如何获取对象的属性,是一个问题。
还有返回值的处理。


先实现最简单 的 插入操作

返回值,返回一个受影响的行数

1、在 mapper 中的 UserInfoMapper 接口中定义 方法

Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔


2、在xml文件中 编写 SQL语句 的相关代码。

Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔


3、进行单元测试

Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔

算了,为了防止你们认为我做假。
我 “污染”一条 王五的信息。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔


上面最简单的实现方式,下面我们来看难一点的实现。

现在我们不想 将 影响的行数,作为返回值了。
我要 插入成功的用户信息的ID!
虽然实现的步骤没有变化,但是想要达到预期的效果,还需要做点事。


先来看第二步:在xml文件中 编写 SQL语句 的相关代码。

Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔

对了,我还扩展一下。
在上述 insert 语句中,最好加一个属性:keyColumn。
更保险!
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
useGeneratedKeys:
这会令 MyBatis 使⽤ JDBC 的 getGeneratedKeys ⽅法来取出由数据库内部⽣成的主键
(⽐如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的⾃动递增字段),默认值:false。

keyColumn:
设置⽣成键值在表中的列名,在某些数据库(像 PostgreSQL)中,当主键列不是表中的第⼀列的时候,是必须设置的。如果⽣成列不⽌⼀个,可以⽤逗号分隔多个属性名称。

keyProperty:
指定能够唯⼀识别对象的属性,MyBatis 会使⽤ getGeneratedKeys 的返回值 或 insert 语句的 selectKey ⼦元素设置它的值,默认值:未设置(unset)。如果⽣成列不⽌⼀个,可以⽤逗号分隔多个属性名称。

简单来说:我们加上 keyColumn 的原因,就是为了确保获取 对应字段。


再来看一步:在 mapper 中的 UserInfoMapper 接口中定义 方法。

返回受影响的行数 和 自增 id
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔


我们测试一下。

带了这一步,我就不给你们演示如何创建 单元测试了。
这都soEeasy的事情了。
另外,我直接上代码。
看不懂,就返回去看前面的。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
有的人可能会有疑问:自增ID,是有什么问题吗?
其实没问题!
这是因为 MySQL 中的 搜索引擎 InnoDB 的机制,就是这样设计的。

学到这里,就可以将一些 简单的servlet 项目,改成 SSM 项目。


查询操作

单表查询

这个就是前面的 第一个 MyBatis 查询 。
它急救室一个单表查询。

单表查询,就是字面意思:
在一张表中查询数据。

这个我就不重复讲了。
来看下面的重点


参数占位符 #{} 和 ${} 的区别

#{}:预编译处理。
${}:字符直接替换

预编译处理是指:MyBatis 在处理#{}时,会将 SQL 中的 #{} 替换为?号,使⽤ PreparedStatement 的 set ⽅法来赋值。直接替换:
而MyBatis 在处理 ${} 时,就是把 ${} 直接替换成变量的值。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
这里需要普及的一点:
MySQL中有两种查询:1、叫做及时查询(字符直接替换);2、预查询(预处理)
及时查询:当了需要执行SQL的时候,才会去执行SQL,
预查询:相对于 及时查询,预查询要多出两步:既然查询的参数会变,那先用一个 ?号占位符 顶替参数,先构造好一个SQL,进行预查询,看看SQL的格式有没有错误。
随后,通过 jdbc 的方式来替换 占位符。这样就能保证SQL的正确性。

你可能觉得这麻烦,但是其实!预查询是为了提高查询效率的。
举个例子:Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔

通过上述的代码,只能证明 #{} 和 ${} 对于 查询条件为整形的查询效果是一样,至于效率,只能意会,毕竟,我们感知不到。
但是!安全性,是可以体现的!下面我们就来演示一下。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔

由此,不难得出结论:#{}支持所有的数据类型,${} 只支持 数值类型
再举个例子,来加深对 预编译【 #{} 】和字符串替换【 ${} 】之间的区别 的理解

头等舱和经济舱乘机分离的故事:
在坐⻜机的时候头等舱和经济舱的区别是很⼤的,如下图所示:
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
⼀般航空公司的客运机都是头等舱和经济舱分离的,头等舱的⼈先登机,登机完之后,封闭头等舱,然后再让经济舱的乘客登机,这样的好处是可以避免有人浑⽔摸⻥,经济舱的⼈混到头等舱的情况,这就相当于预处理,可以解决程序中不安全(越权处理)的问题。

⽽直接替换的情况相当于,头等舱和经济舱不分离的情况,这样经济舱的乘客在通过安检之后可能越权摸到头等舱,如下图所示:
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
这就是 直接替换【 ${} 】 所带来的缺陷。
就是说: SQL 没有正确访问它应该访问的地方。
这就是 SQL注入的安全问题。

而且,由于头等舱的票很贵,买的很少。
通常是坐不满的,因此,上述这种 经济舱的人 做到 头等舱也是常有的 。
最好的方法,就是前面说的一下,先让头等舱上机,上完之后。
关闭头等舱的入口,然后,经济舱的人开始上机。
这样就不会存在有 “ 误入 ” 的情况了。

有些人的素质是真的低,买了低价的票,却想着享受高价的服务。
如果不让他如愿,它还恶意举报 航空公司。

所以,出现这种问题的根本原因:就是等级没有分明。
头等舱的门,就相当于是一个界限。
将 头等舱 与 经济舱 分离了。

有的人可能会说:加一个检票员来检查票据。
这样不也能解决问题嘛。
但是!你遇到不讲理的怎么办?
它就是要坐!
它有一大堆歪理由。。。。
这时候,你想把它请出去,非常难!
容易产生纠纷。
所以,不推荐这样去做。

因此,正如前面所说:在登机的时候就把 人员分开。
让优先级高的线上,就不会存在纠纷问题。
同样也很高效。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
这样就不用做第二次校验,也就不会产生纠纷问题。
即使 头等舱的用户来迟了,叫一个空乘服务人员,专门领他过去,就行了。
领进去,再把门一关。
经济舱的人还是混不进入的。

也就是说:每个人都只经历了一次检验机票,就完成了登机。

那么,问题来了。
既然 #{} 这么无敌,那么,${} 是不是就可以滚蛋了?

NO!
存在,即有意义!【蚊子除外!】
下面,我们来看 ${} 的使用场景。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
其实,与其说是 ${} 的优点,不如说是它的专长!
毕竟 这任务,#{} 做不了,只有它能做!

前面不是说:
使用 ${} 存在 SQL 注入的问题嘛?
现在,哪怕是处于 ${} 的主场,依然也存在着这个问题!
那如何处理呢?

注意事项:
当不得不使用 ${} 时,那么一定要在业务代码中,对传递的值,进行安全校验!
就是说:当代码执行到 mapper 这一层,就没救了!
只能 “ 上战场 ”了。。背水一战。
所以,一定要在业务代码中,对数据校验。
Controller 的作用就体现出来了!
因为 它就是负责对数据的校验。
结合现在的情况:就是对 传递过来的 order 进行 判断非空,长度不能为0,甚至对内容进行校验,是否是 desc 和 asc,其中的一个。

结合生活案例:
我们在上地铁,火车,飞机等公共交通工具的时候,为什么在我们乘坐之前,就开始检查我们的包裹,而不是 上车的时候,检查包裹。
这我们的 ${} 的校验数据,是同一个道理。
为了保证传递的数据的安全性以及正确性。【确认没有违规物品,保证乘客的声明安全】


${} 的经典问题:SQL注入问题

Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔

由上面的例子,相信大家对 SQL注入问题,有了一定了解。
SQL注入问题,就是在我们没有对 传递数据进行校验的前提下。
当我们处于一些特殊场景的时候,可以在不满足获取条件的情况下,获取私密信息。
这对用户信息和 财产的安全隐患是非常巨大的!!!
这就是安全漏洞啊!!!

所以,请一定一定一定切记!在处理一些重要信息的时候,如果使用了 ${},请在 Controller 层,对数据进行严格校验!

我们再来试一下:把 $ 改成 # ,效果会如何?
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
由此,不难得出结论:
使用 #{} 是不存在 安全漏洞滴!!!
是非常nice的。


like 查询 - 特殊情况

前面的问题,由于关键字,就那么几个。
直接穷举,很容易在 Controller 层里面 判断数据的正确性。

但是!模糊匹配能穷举吗?
很显然是不能的!
如果数据有个几百万,那我们不得嗝屁!
下面,演示一下。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
此时,问题也就随之而来了!
在进行模糊匹配查询的时候,不能使用 #{} ,否则会报错!
而改用 ${} ,有穷举不了所有的情况。
无法做到完美验证数据的正确性和安全性。

那么,怎么解决这一个问题呢?
使用concat拼接方法,就可以解决问题了。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔


#{} 和 ${} 的区别总结

1、定义不同:#{} 预处理:而 ${} 是直接替换

2、使用不同:#{} 适用于所有类型的参数匹配;但 ${} 只适用于数值类型。

3、安全性不同:#{} 性能高,并且没有安全问题;但 $ {} 存在 SQL 注入的安全问题。

4、使用场景不同:
当传递的是一个 SQL 关键字 的时候,只能 使用 ${} 。
当传递的是一个字段,总之,就是需要获取到参数类型 与 内容,只能使用 #{}。
(PS:数字类型,#{} 和 ${} 都是可以使用的!)

5、 ${} 不能用于 模糊匹配查询;而 #{} 需要搭配 concat 才能在模糊匹配中使用。


多表查询

前置知识

返回类型:resultType

这个大家都很熟悉,我们已经在前面的示例中,使用了很多次。
用于指定 SQL 查询结果 映射的对象类型。
但是!这里有一个细节!
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
除了修改 实体类属性名称,还有一个方法:使用 下面即将介绍 resultMap。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔

返回字典映射: resultMap

resultMap 使用场景:

1、字段名称和程序中的属性名不同的情况,可以使用 resultMap 配置映射、
这个上面演示过了,就不再演示了。

2、一对一和多对多关系可以使用 resultMap 映射并查询数据。


resultType && resultMap 的区别

1、在对象属性名称 与 数据表字段名称,相同的情况下:
使用 resultType 比 resultMap 更爽!

2、在对象属性名称 与 数据表字段名称,不同的情况下:
使用 resultMap 可以指定 不同名称的 字段与属性 的映射关系。


有了 resultMap 和 resultType 的基础,我们下面就可以真正开始进行多表查询的操作了。

在 MyBatis 中,支持的多表查询有两种:
1、 一对一关系:以博客来说,一篇博文只能有一个作者,文章和作者的关系就是一对一。
2、一对多关系:一个作者可以是 多篇博文的作者。都是他写的嘛!作者 和 文章是 一对多的关系。


MyBatis 多表查询:一对一关系

下面,我就来模拟实现一下 一对一的关系。
but!在此之前,我们需要创建关于文章表的实体类。
我们前面只是创建一个 用户表的实体类。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
在进行查询之前,我们先来看一下 articleInfo 表中,有几条信息,信息的内容是什么,这样方便我们后面写代码。

Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
如果你们的content 出现乱码,反正就不是中文。
你就可以参考这篇文章MySQL第二讲,在文章的最后讲了如何配置字符集的问题Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔

下面,我们去mapper 包中,在创建一个 MyBatis 的接口。
实现根据文章的 id 查询到文章的详情信息。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
咋一看,好像没有问题。但是确实有问题!
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
这就很好奇了,文章的 uid 是 1,对应着 用户表中 admin 用户。
就是说:文章的作者,确实是存在的。
那为什么 userinfo是 null 呢?
原因很贱,我们的实体类有这哥属性,但是文章表里没有这个字段啊!
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
这个时间,resultMap 就上场了。因为 resultType 它不行!

可参考对数据表进行“增删查改”的进阶操作
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
此时,我们就实现了一对一查询。(存在问题)
【association 标签,就是用来实现一对一情况的多表查询】
虽然存在一些问题:
UserInfoMapper 中 resultMap 配置的字段信息不完整。
导致:虽然查询结果的信息是完整,但是没有完整映射到 ArticleInfoMapper 的 userinfo 属性中。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
这里可以体现出一个结论:
在 本身 xml 文件,是可以不用映射所有属性的信息。
因为 它是自己调用自己,所以,不需要 resultMap 将属性全部映射,都能自动完成所有属性的映射。
而 想要在一个 resultMap 中 调用 另一个 resultMap 中信息,只能是它映射了的信息。
否则无法获取。
所以,我们来讲 UserInfoMapper 中 resultMap 对于属性的映射补全,再来看个效果。Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
这里还存在着问题:
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
到头来,结果发现:还是存在问题的。
当一个属性,在两个数据表中都存在时,默认读取的是 本身的字段值。
也就是说:不光是上面的两个,我们演示的那个情况,出现的那四个属性都是这个情况。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
下面,我们来分析解决这个问题。
同时加深对这个问题的印象。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
这个时候,我们才真正完成了 一对一关系的多表查询!!!!!


MyBatis 多表查询:一对多关系

一对多关系:⼀个⽤户可以是多篇⽂章的作者。
⼀对多需要使⽤ < collection > 标签,⽤法和 < association > 是一样的 .

为什么使用 collection标签,其实很理解。
看中文意思就明白了!
association 的中文就是关联,放到使用场景中,就是将 2个表联系起来。

collection 的中文意思就是 收集(把它理解为集合),将多个表收集起来,整合。
当然,这些表,肯定都与一张表有关系,不管怎么叫做 一对多?
放在实例中,多个表对应着多篇文章,
这多个表中有一个相同字段,且信息是一样的。【作者id是一样,即是同一个人写的】
由这个作者id,就可以把所有文章(是他写的文章)给映射起来,建立关系。
这就是 一对多 关系。

在实现一对多关系查询之前,我们先来做一些准备工作。
首先我们来用户表的实体类进行处理。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
下面我就来实现这个一对多的关系查询。。
实现一个 根据用户id,获取用户和他缩写的文章信息。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
换句来说:
一对多关系的查询,相比于 一对一关系的查询,就是把 resultMap 中的 association 标签,换成了 collection 标签。
里面的内容一点都没改。


复杂情况:动态SQL使⽤

动态 sql 是MyBatis的强⼤特性之⼀,能够完成不同条件下不同的 sql 拼接。
可以参考官⽅⽂档:Mybatis动态sql
进去之后就是这个页面
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
你会发现 动态SQL,被拎出来,放目录里,说明什么?
说明它重要啊! 不然,我也不会给它一级标题啊。


动态标签 - < if >标签

在注册⽤户的时候,可能会有这样⼀个问题,如下图所示:
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
回顾我们之前实现的功能查询,你会发现参数全部都是确定的,就是写死了的。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
都是抱着那种前端一定会传给我数据的 “ 态度 ”,也就是认为一定会拿到必要参数!
但是!在实际业务场景中,会存在一些非必传的参数。
就是说:必要参数,有时候是不会传输的,很有可能传过来的参数,是没有的。
那么,面对这种情况,我们在程序中该如何处理?
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
下面,我们就来具体实现这个业务。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
但是存在着一个问题:如果参数很多,而且!每一个参数都是非必传参数。
那么,我们需要对每一个参数进 if 标签来判断。
这就非常的麻烦!可能会多敲字符,导致SQL出现问题。
这个时候,就需要借助 trim 标签了。
具体怎么使用,看下面对 trim 标签的介绍和使用。


动态标签 - < trim >标签

之前的插⼊⽤户功能,只是有⼀个字段可能是非必传,如果有多个字段,⼀般考虑使⽤ < trim >标签 结合 < if >标签,对多个字段都采取动态⽣成的⽅式。
< trim >标签中有如下属性:
prefix:表示整个语句块,以prefix的值作为前缀
suffix:表示整个语句块,以suffix的值作为后缀
prefixOverrides:表示整个语句块要去除掉的前缀
suffixOverrides:表示整个语句块要去除掉的后缀

说白了,trim 就是为了方便程序员 去除 开头 /末尾 的 某个符号
也就是说:在 开头/结尾 ,多写了这个符号,是没有问题的。
因为 trim 会将它删除掉。
如果没多写,也不会影响 程序的运行。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
下面我们来实战演练一波。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔


动态标签 - < where >标签

传⼊的⽤户对象,根据属性做where条件查询,⽤户对象中属性不为 null 的,都为查询条件。
如user.username 为 “a”,则查询条件为 where username=“a”:
where标签,主要作用:实现查询中的 where 替换的。
什么意思呢?
原先我们写的 where 是 SQL 的一部分,是SQL的关键字。
现在我们使用 MyBaits 提供是 where 标签来代替 SQL 中 where 关键字 。

那么,有的人可能会有疑问; 这替换跟没替换一样,还多敲 几个箭头。
因此,会产生一个问题: 使用 where 标签 的好处都有哪些?
不然不会创造出一个 where标签出来。
如果没有任何查询条件的情况下,where 标签 可以实现 隐藏 SQL 的 where 的SQL部分;但如果存在查询条件,那么会生成 where的 SQL,并且 where 标签可以自动的去除最后一个 判断条件的 and 字符。

也就是说:我们在拼接多个条件的时候,有几个条件涉及到非必传参数。
会有一个什么情况呢?
通常 我们拼接多个条件的时候,要么使用 or(或逻辑),要么使用 and(与逻辑)。
A and B;
当 A 条件,缺少判断的参数的时候,就会将 A 和 后面 的 and 去除掉。
这个and也就是最前面一个 and 嘛。
就变成一格判断条件的 SQL了。

下面,我们来实战一下。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
虽然比直接敲SQL,要复杂一点点。
但是,构造 SQL的灵活性,大大提高!

还有一个去除 and 的功能,对吧?
我们再来搞一下。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
如果 and 放在后面,就是说 and 后面没有判断条件,或者说 缺少 参数,无法构造 where SQL。会出现什么状况?
我已经帮你们测了,报错!
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
其实这个问题也很好解决。直接给你们演示个大概。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
以上标签也可以使⽤ < trim prefix=“where” prefixOverrides=“and” > 替换。
无非就是 少用几个属性。


动态标签 - < set >标签

根据传⼊的⽤户对象属性来更新⽤户数据,可以使⽤标签来指定动态内容。
UserMapper 接⼝中修改⽤户⽅法:根据传⼊的⽤户 id 属性,修改其他不为 null 的属性:
这么说吧。set 标签,和 where 是一样。
都是替代 SQL 中的关键字。
set 就是 update 操作所需要使用的关键字。
而且,也是和 if 标签 配合使用的。

而且和 trim 标签一样。可以去掉最后一个符号(一般都是逗号)。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
实战演示一波。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
那如果没有 逗号呢?会是什么效果?
下面我们把三个非必传参数都传递。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
以上标签也可以使⽤ < trim prefix=“set” suffixOverrides=“,” > 替换。


动态标签 - < foreach >标签

对集合进⾏遍历时可以使⽤该标签。< foreach >标签有如下属性:
collection:绑定⽅法参数中的集合,如 List,Set,Map或数组对象
item:遍历时的每⼀个对象(集合中的元素)
open:语句块开头的字符串(类似 trim的 prefix)
close:语句块结束的字符串(类似 trim的 close)
separator:每次遍历之间间隔的字符串(间隔符)
【PS:foreach 标签,是 trim 所不能替代的!】

应用场景:
通常我们要删除数据库中的信息,不是一条一套的删。
而是一删一大把。
比如:
我们要根据id来删除 记录,我们就把这些id 弄成一个结合。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
此时就可以通过 foreach 标签,进行遍历传参,删除对应的信息。
这样做的效率就很高,一次调用,就可以将需要删除的数据全部删除。

实战演示一波。
Java进阶 - MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔

相关文章

暂无评论

暂无评论...