目 录
- 一.MyBatis 是什么?
- 二.为什么要学习 MyBatis?
- 三.怎么学MyBatis?
- 四.第⼀个MyBatis查询
- 1.创建数据库和表
- 2.添加MyBatis框架支持
- 配置数据库的连接信息(连接哪台数据库)
- 配置 mybatis XML文件存放位置和命名规则
- 3.添加业务代码
- 使用 MyBatis 实现查询功能:
- MyBatis 传参查询
- 五.增、删、改操作
- 增加用户操作
- MyBatis 添加得到用户的自增 id:
- MyBatis 删除功能:
- MyBatis 修改功能:
- 简单的项目运行
- 六.查询操作
- 单表查询
- 参数占位符 #{} 和 ${}
- SQL 注入问题
- like 查询(模糊查询)
- 返回类型 resultType
- 返回字典映射:resultMap
- 多表查询
- 一对一的表映射
- 七.动态SQL使用
- `<if>` 标签
- `<trim>`标签
- `<where>`标签
- `<set>`标签
- `<foreach>`标签
一.MyBatis 是什么?
MyBatis 是⼀款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 去除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
简单来说 MyBatis 是更简单完成程序和数据库交互的工具,也就是更简单的操作和读取数据库工具
二.为什么要学习 MyBatis?
对于后端开发来说,程序是由 后端程序和数据库 两个重要的部分组成的
而这两个重要的组成部分要通讯,就要依靠数据库连接工具,那数据库连接工具有哪些?
比如之前的 JDBC,还有将要介绍的 MyBatis,那已经有了 JDBC 了,为什么还要学习 MyBatis?这是因为 JDBC 的操作太繁琐了
对于 JDBC 来说,整个操作非常的繁琐,我们不但要拼接每一个参数,而且还要按照模板代码的方式,一步步的操作数据库,并且在每次操作完,还要手动关闭连接等,而所有的这些操作步骤都需要在每个方法中重复书写。
学习 MyBatis 的真正原因,它可以帮助我们更方便、更快速的操作数据库。
三.怎么学MyBatis?
- 配置 MyBatis 开发环境
- 使用 MyBatis 模式和语法操作数据库
四.第⼀个MyBatis查询
MyBatis 在整个框架中的定位,框架交互流程图:
MyBatis 也是⼀个 ORM 框架,ORM(Object Relational Mapping),即对象关系映射。在面向对象编程语言中,将关系型数据库中的数据与对象建立起映射关系,进而自动的完成数据与对象的互相转换:
- 将输入数据(即传⼊对象)+SQL 映射成原生 SQL
- 将结果集映射为返回对象,即输出对象
ORM 把数据库映射为对象:
- 数据库表(table)–> 类(class)
- 记录(record,行数据)–> 对象(object)
- 字段(field) --> 对象的属性(attribute)
一般的 ORM 框架,会将数据库模型的每张表都映射为一个 Java 类。
也就是说使用 MyBatis 可以像操作对象一样来操作数据库中的表,可以实现对象和数据库表之间的转换
1.创建数据库和表
-- 创建数据库
drop database if exists mycnblog;
create database mycnblog DEFAULT CHARACTER SET utf8mb4;-- 使⽤数据数据
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
) default charset 'utf8mb4';-- 添加⼀个⽤户信息
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);
在数据库中输入以上信息,然后查询表:
看到我们数据成功导入进去了。
2.添加MyBatis框架支持
配置数据库的连接信息(连接哪台数据库)
此处用户名和密码都是自己 MySQL 数据库的,如果做完下面第二步程序没有启动成功,这里的 useSSL=false 去掉再试试
spring:datasource:url: jdbc:mysql://localhost:3306/mycnblogcharacterEncoding=utf8&useSSL=falseusername: rootpassword: *****driver-class-name: com.mysql.cj.jdbc.Driver
如果使用 mysql-connector-java 是 5.x 之前的使用的是 “com.mysql.jdbc.Driver”,如果是大于 5.x 使用的是 “com.mysql.cj.jdbc.Driver”。
配置 mybatis XML文件存放位置和命名规则
配置 mybatis xml 的文件路径,在 resources/mybatis 创建所有表的 xml 文件
此时程序成功执行,说明配置成功:
此处还可以加上配置打印 MyBatis 执行的 SQL,可以在控制台中看到执行的 SQL 信息:
mybatis:mapper-locations: classpath:mybatis/**Mapper.xmlconfiguration: # 配置打印 MyBatis 执行的 SQLlog-impl: org.apache.ibatis.logging.stdout.StdOutImpl
3.添加业务代码
按照后端开发的工程思路,也就是下面的流程来实现 MyBatis 查询所有用户的功能:
我们要在 mybatis 中创建一个 接口和 xml 文件
-
接口 (当前类的所有方法声明)【给别人调用】
-
xxx.xml (它和接口一一对应,一个类会有一个接口和一个对应实现操作的 xml 文件) 存放 sql
使用 MyBatis 实现查询功能:
- 创建一个接口
- 创建与上面接口对应的 xml 文件(实现类中的所有方法)
注意创建 xml 的目录和名称一定要和配置相对应(xml 名字可以随意取,但是后缀要和配置后缀一样)
将 mybatis-xml 模板的内容复制到新建的 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="com.example.mybatisdemo.mapper.UserMapper"></mapper>
- 使用单元测试成功与否
可以看到数据插入成功,证明我们数据插入成功了
MyBatis 传参查询
把 id = 1 的用户信息从 mysql 中传进来
注解 @Param 把 uid 赋值给 id (后续在 xml 中传参是使用 @Param 里的参数名)
此处使用 uid 名称是为了区分在 xml 的 where 条件中的传参用的是谁,一般代码中 注解 @Param 里的参数和要赋值的参数一般是一致的
对应的 xml 中修改方法名和传参条件
生成单元测试添加业务代码打印:
此处 id = 1 可以拿到数据库中 id = 1 的数据,id 是谁就拿谁的数据
执行结果 :
五.增、删、改操作
增加用户操作
在 MySQL 中新增文章表
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
)default charset 'utf8mb4';
可以看到新增成功
- 创建文章实体类
注意此处类中的属性名要和数据库中的字段名一一对应
- 创建一个接口
写一个增加的方法:
注意事项:此处我们操作的是一个对象,所以此处不建议使用 @Param 注解,@Param 注解对于非对象来说(单纯的属性),很好使,直接把属性名字写进去即可,对象用 @Param 就考虑到 xml 会验证 @Param ,很容易出问题
建议非对象使用 @Param 注解,如果对象非要使用 @Param 注解,那么在 sql 语句 values 插入的时候得标明是 @Param 注解 对象里来的
- 创建与接口相对于的 xml 文件
- xml 文件名后缀要和 yml 中的约束一致
- 修改当前 xml 实现接口的路径
- 此处 insert 就只需要 id 即可,不需要返回类型
- sql 语句每个单词之间都可以直接回车,xml 会自动识别 sql 语句连贯
- values 后面的数据是来自类属性
- 设计单元测试
测试结果:
MyBatis 添加得到用户的自增 id:
接口类继续在新增里加一行:
xml 里:
单元测试:
测试结果:
我这里次数是我自己多运行了几次,所以自增结果多几条
MyBatis 删除功能:
接口类:
xml 文件里:
单元测试:删除第二条数据
MyBatis 修改功能:
接口类中:
xml 中:
单元测试中修改数据:(把第一个数据改为 “我怎么这么帅”)
MySQL 中查看:
简单的项目运行
由于我们可以知道数据持久层是被服务层调用的,服务层又是被控制器层调用,这样才能实现 SpringBoot 项目的完整运行
添加服务层:
创建一个服务层包,里面实现对 UserMapper 的调用,直接调用了 UserMapper 里的 getAll 方法
创建一个控制层:
里面实现了一些路由,还有调用服务层的 getAll 方法
此时运行 SpringBoot 项目,访问网站路由就可以看到数据库我们想拿到的数据
六.查询操作
单表查询
上面实现了根据用户 id 查询用户信息的功能
参数占位符 #{} 和 ${}
- #{}:预编译处理。
- ${}:字符直接替换。
在 id 查询中 xml 里也可以把 # 符换成 $ 符号,发现程序也可以运行
然后用 # 试一试根据用户 name 完全匹配查询发现也可以,再试试 $ 符发现运行报错了
报错:
日志里说没有找到 admin 的用户,可是在数据库中刚才用 # 符找到了,在 MySQL 中也能制造相同的问题:
预编译处理是指:MyBatis 在处理 #{} 时,会将 SQL 中的 #{} 替换为?号,使用 PreparedStatement的 set 方法来赋值。直接替换:是MyBatis 在处理 ${} 时,就是把 ${} 替换成变量的值。
此时在我们的 ${} 外面像 MySQL 一样包裹一个单引号就可以了
此时程序就可以正常运行
哪是不是 #{} 也可以外面包裹一层单引号呢?答案是不行
在 MySQL 中,这样的写法虽然显示的是一样的,但是上面是 int 类型,下面是 String 类型,String 类型 MySQL 做了隐式转换,转化为 int 类型了,那么它就不会去走索引了,导致查询性能非常非常低
举例:对于在购物平台有的选项是价格从高到低,有价格从低到高的那些选项是怎么实现的呢?
例如我们实现一个根据排序条件进行排序,正序排序:
单元测试结果:
MySQL 中也有类似的错误:
asc 是 MySQL 中的关键字,不需要加单引号,此时我们的 $ 符就派上用场了,把 # 换成 $ 符就可以运行成功了
SQL 注入问题
例如我们 MySQL 数据库正常情况下:
只有输入正确的用户名和密码才能知道个人信息,但是通过一串字符串就可以破解:
' or 1='1
我们可以分析,第一个单引号直接让前面的条件 username='admin' and password=''
没了结果,但是呢,后面跟了个 or
,要么前面的条件 username='admin' and password=''
满足,要么 1='1'
条件满足,根据前面我们可以知道 MySQL 会进行隐式类型转换,让后面的等式恒等,让等式永远成立,这样我们的数据就被窃取了
我们自己写一个代码测试:
运行发现我们的程序拿到了数据:
把 $ 符换成 # 符之后运行结果:
总结:
$ 代表字符直接替换 和 # 代表预编译处理 最大的区别就是使用 $符存在安全隐患,SQL 注入的安全问题,而 # 符不存在这个问题,使用 # 符绝大部分的正常的传参都是可以处理的,而在某些特殊情况下,如传 MySQL 关键字的时候,就可以使用 $ 符,但是要注意 SQL注入问题
like 查询(模糊查询)
例如我们查询一个带有 m 字母的名字
<select id="findUserByName2" resultType="com.example.demo.model.User">select * from userinfo where username like '%#{m}%';</select>
原本我们期待的 SQL 语句是:
select * from userinfo where username like '%m%';
此时程序里就会把 m 当做字符串处理
select * from userinfo where username like '%‘m’%';
这样程序就会报错,执行不了
那是不是可以用 $ 符查询呢?也不行,因为当我们要用 $ 符的时候,控制器 Controller 会先扫描 asc 或者 desc 按照排序来验证,穷举每个例子,但是例如此处我们要查询名字的时候,无论是正序还是倒序都是无法排序穷取的,所以此处可以使用 mysql 的内置函数 concat() 来处理,实现代码如下:
<select id="findUserByName3" resultType="com.example.demo.model.User">select * from userinfo where username like concat('%',#{username},'%');
</select>
返回类型 resultType
绝大数查询场景可以使用 resultType 进行返回,例如我们上面的代码都是 resultType 进行返回:
它的优点是使用方便,直接定义到某个实体类即可,但是无法处理数据库字段和我们 Java 程序里类属性名称不一致的问题
返回字典映射:resultMap
程序中的属性和数据库中的字段名不一致,可使用 resultMap 来解决:
resultMap 使用场景:
- 字段名称和程序中的属性名不同的情况,可使用 resultMap 配置映射;
- 一对一和一对多关系可以使用 resultMap 映射并查询数据。
例如我们类属性名字是 name,字段名字是 username,那么可以按照如下格式就能实现他们的匹配
<resultMap id="BaseMap" type="com.example.demo.model.UserInfo"><id column="id" property="id"></id><result column="username" property="name"></result><result column="password" property="password"></result>
</resultMap><select id="getAll" resultMap="BaseMap">select * from userinfo where id=#{id}
</select>
- id 主键
- result 普通列
- column 匹配字段
- property 匹配类属性
注意这里 resultMap id 可以随意取,不同的 xml 也可以使用相同的名字,不在一个 xml 中不影响,type 就是配置当前 xml 要实现接口的路径,就是 resultType 路径,然后 resultMap 引用 resultMap id 即可。尽量把所有的字段和属性匹配都写上去
多表查询
一对一的表映射
例如在文章 articleinfo 中:
想要把 Userinfo 里的作者 admin 添加进去,我们只需要两步:
- 连接表需要查询的字段添加到实体类
- 使用联合查询进行功能查询即可
单元测试:
测试结果:
如果在这里我们的属性名和字段名不一样,我们有两种方法解决,第一种就是 resultMap,第二种就是在 SQL 语句里面把字段名重新命名成我们要的名字,如:
七.动态SQL使用
<if>
标签
在某些特定的场景,例如注册用户或者填一些信息表的时候,我们常会看到必填项和选填项,那么选填项可填可不填,那就很多种组合了,选填项没填那我们传参的时候是什么也不写呢还是传一个 null 呢?
例如在 MySQL 里我们看一下:
可以看到张三 photo 传了 null 进去,而李四 photo 什么也没传,此时我们条件查询 photo 为空就只能查到李四,photo 为 null 发现就只能查到张三,说明两者并不一样,此时就关系到我们的 if标签 查询了
在接口里写相应的方法:
使用 if 标签的语法格式对 photo 进行 SQL 插入:
实际上就是走的 if 语句进行简单判定,是否加不加变量
- 注意:我们的变量位置是要放在中间的,如果放在后面,当变量为 null 的时候,逗号就在最后一个参数后面,跟括号相接了,就不符合 SQL 语法规范就会报错,如果字段都是变量可选的,导致都没有位置可选,可以看后面
<trim>
标签 的处理
在单元测试里我们输入以下信息:
-
王五:
-
老六
王五和老六的 photo 传参不一样,一个是 null ,一个是数值
查看数据库表:
<trim>
标签
之前的插入用户功能,只是有⼀个字段可能是选填项,如果所有字段都是非必填项,就考虑使用 <trim>
标签结合 <if>
标签,对多个字段都采取动态生成的方式。
<trim>
标签中有如下属性:
- prefix:表示整个语句块,以 prefix 的值作为前缀
- suffix:表示整个语句块,以 suffix 的值作为后缀
- prefixOverrides:表示整个语句块要去除掉的前缀
- suffixOverrides:表示整个语句块要去除掉的后缀
例如我们上一个例子,在不考虑 MySQL约束的情况下,假如我们的 username 和password 都可以作为变量来选择,那么具体的语法就如下:
prefix 让整体前面有个括号,suffix 让整体后面有个括号,suffixOverrides 让只要后面结尾有逗号就去掉,所以就不用担心最后的变量是 null 让逗号结尾和括号在一起导致不符合语法规范了。
<where>
标签
传入的用户对象,根据属性做 where 条件查询,用户对象中属性不为 null 的,都为查询条件
语法格式:
and 是被 where 自动去除的,后面每个查询条件前都加 and,
在单元测试里也跑成功了:
where 做的两件事:
- 当 where 标签里有数据的时候,会在 SQL 语句后面拼接一个 where 进行查询
- 第一个 if 条件前面是不是有 and,有就去掉,让 sql 符合数据库的执行标准
<set>
标签
根据传入的用户对象属性来更新用户数据,可以使用 <set>
标签来指定动态内容(和where 用法一致)。
<update id="updateById" parameterType="com.example.mybatisdemo.model.Userinfo">update user<set><if test="username != null">username=#{username},</if><if test="password != null">password=#{password},</if><if test="nickname != null">nickname=#{nickname},</if><if test="sex != null">sex=#{sex},</if><if test="birthday != null">birthday=#{birthday},</if><if test="head != null">head=#{head},</if><if test="createTime != null">create_time=#{createTime},</if></set>where id=#{id}
</update>
<set>
标签也可以使⽤ <trim prefix="set" suffixOverrides=",">
替换。
set 标签做的两件事:
- 相当于帮我们执行了 SQL 语句里的 set 关键字
- 去掉了属性后面带有的逗号,让 sql 符合数据库的执行标准
<foreach>
标签
对集合进行遍历时可以使用该标签。<foreach>
标签有如下属性:
- collection:绑定方法参数中的集合,如 List,Set,Map 或数组对象
- item:遍历时的每⼀个对象
- open:语句块开头的字符串
- close:语句块结束的字符串
- separator:每次遍历之间间隔的字符串
例如删除如下 id 为 2,3,4,9 的数据
接口方法:
里面的类型是根据数据库中的字段类型来的
<foreach>
标签 使用方法,collection 里的是方法对象, item 里的是遍历的时候取的临时对象,open 就是开头加个字符串,close 就是末尾加个字符串,separator 就是每个遍历之间加的分割字符串
单元测试:
测试结果: