文章目录
- 1. MyBatis是什么?
- 2. 为什么要学MaBatis?
- 3. MyBatis环境搭建
- 4. MyBatis的使用
- 4.1 简单示例
- 4.2 获取动态参数
- 4.2.1 ${xxx}获取动态参数
- 4.2.2 #{xxx}获取动态参数
- 4.2.3 #{xxx}与${xxx}获取字符串类型数据
- 4.2.4 sql注入问题
- 4.2.5 模糊查询like
- 4.2.6 #{}与${}区别总结
- 5. 修改操作(增删改)
- 6. 查询操作
- 6.1 resultType与resultMap
- 6.2 单表查询
- 6.3 多表查询
- 7. 动态SQL
- 7.1 \<if>标签
- 7.2 \<trim>标签
- 7.3 \<where>标签
- 7.4 \<set>标签
- 7.5 \<foreach>标签
1. MyBatis是什么?
MyBatis是一款持久层框架,它可更简单的完成程序与数据库之间交互的操作,它可以更简单的操作与读取数据库,它还支持自定义SQL,存储过程及高级映射
2. 为什么要学MaBatis?
对于后端开发来说,完整的程序由以下两部分组成:
- 后端程序
- 数据库

后端程序与数据库之间的通讯,就得依靠数据库连接工具,像之前学习的JDBC,但是因为JDBC使用起来太繁琐了,简单回顾使用JDBC操作数据库的步骤:
- 创建数据库连接池DataSource
- 使用DataSource获取数据库连接Connection
- 编写预编译的sql
- 使用Connection及sql创建操作命令对象Statement
- 替换占位符
- 使用Statement执行sql
- 如果是查询,则返回并处理结果集
- 释放资源
所以呢,使用MaBatis简化上述操作,方便快速的操作数据库
3. MyBatis环境搭建
创建Spring Boot项目的时候,添加MySQL驱动依赖与MyBatis框架依赖

创建好项目后,如果直接启动项目会报错,因为启动项目时候要连接数据库,此时我们还没有配置数据库源
在配置文件application.properties中配置数据库连接信息
# 配置数据库连接信息
spring.datasource.url=jdbc:mysql://localhost:3306/blog?characterEncoding=utf8&useSSL=false
spring.datasource.username=root
spring.datasource.password=xiaobai520..@@@
# 配置数据库驱动
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
在配置文件application.properties中配置MyBatis的xml文件路径
# 配置mybatis xml路径
# 在根路径下有mybatis文件夹,里面有xxxMapper.xml文件
mybatis.mapper-locations=classpath:/mybatis/*Mapper.xml
配置MyBatis中sql执行打印
#配置mybatis sql执行打印
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
# sql打印日志的级别为debug,但是spring boot默认的打印级别为info
# 设置com.example.demo包下的打印级别为debug
logging.level.com.example.demo=debug
4. MyBatis的使用
4.1 简单示例
我们按照下面的流程来完成MyBatis操作数据库的功能

MyBatis模式开发由两部分组成:
interface:让其他层可以注入使用的接口xxx.xml:具体的sql(可认为它是interface的实现)
具体步骤
创建Mapper接口

创建对应的xml文件

在xml中编写sql

创建service类调用接口中的方法

在controller类中调用service方法

启动项目,在浏览器输入访问地址,可以看到返回一条数据

4.2 获取动态参数
4.2.1 ${xxx}获取动态参数
上述的示例中,查询所有用户的信息,那此时如果想获得id为1的用户的信息该怎么操作?那就要在接口的方法中传递参数了

@Param与@RequestParam用法差不多,也是获取并赋值的意思,并且要求必传,也就是要必须能拿到@Param中的键对应的值
此时xml中的sql应该这样写:

使用${xxx}表示传递的参数,此时xxx必须与@Param中的键对应,如果接口方法中没有用@Param修饰形参,那xxx必须与形参名称对应
在测试类中运行结果如下:


发现使用${}获取动态参数会直接替换
4.2.2 #{xxx}获取动态参数
将sql改为使用#{xxx}的方式替换

再次执行测试类看打印结果:


发现使用#{}获取动态参数是采用占位符也就是预编译的方式替换
4.2.3 #{xxx}与${xxx}获取字符串类型数据
从上述的示例,获取数据库字段类型为int来看,使用这两种方式没有什么区别
此时使用名称获取用户数据,也就是数据库中字段类型为varchar,先使用#{}获取参数

启动测试,观察打印结果:发现正确获取到用户信息

将获取参数方式改为${},再观察打印结果:发现此时报错

原因:${}方式是直接替换,此时sql为select * from user where username=admin,但是正确的sql应该为select * from user where username=‘admin’,程序中执行的sql,admin没有带引号
如果使用KaTeX parse error: Double superscript at position 21: …数的话,我们可以手动加上`' '̲`,再观察打印情况 
测试类,使用正确的用户名和密码:可正确获取用户数据


测试类,将用户密码改为错误的,观察打印结果:获取不到用户数据

测试类,使用错误的用户密码 ' or 1='1,观察打印结果:

发现尽管输入错误的密码,也是可以获取到用户数据,这就是典型的sql注入问题
我们再使用#{}方式获取参数,看看是否也可以获取到数据

发现使用错误的密码获取不到数据,也就是使用#{}不存在sql注入问题
4.2.5 模糊查询like
使用#{}进行模糊匹配

发现程序会报错,因为使用#{},sql变为select * from user where username like '%'a'%',因为#{}使用预编译的方式,对于字符串类型会加上一层引号,所以导致sql错误
此时可以使用concat拼接函数改造sql


发现如果对于like模糊查询,使用#{}的时候得结合concat()函数
除此外,我们可以使用${},因为${}是直接替换sql,所以不存在问题


4.2.6 #{}与${}区别总结
- ${}是将参数直接替换到sql中,#{}是使用占位符也就是预编译的方式
- 当参数为数值类型时,两种方式没区别
- 当参数为字符串类型时,#{}使用占位符预执行,${}直接替换为sql,不加单引号,所以${}存在sql注入问题
- 当参数为sql本身的时候,使用${}
5. 修改操作(增删改)
增加insert

打印结果:

观察数据库是否真正插入:发现插入数据库数据成功

我们在测试类中的方法默认是会持久化到数据库的,如果在方法加上 @Transactional,表示开启事务,则在方法执行结束会进行回滚操作,那么此次对数据库的操作将不会持久化
上述例子表示默认情况返回的是受影响的行数,但是我们增加一条数据时,想返回主键id,则可以按照如下方式进行操作

测试方法如下

打印结果:

useGeneratedKeys:开启还是关闭取出由数据库默认生成的主键,默认是falsekeyProperty:唯一标识对象的属性,useGeneratedKeys获取的主键值赋值给keyProperty设置的属性keyColumn:设置⽣成键值在表中的列名,如果⽣成列不⽌⼀个,可以⽤逗号分隔多个属性名称
删除delete

更新update

6. 查询操作
6.1 resultType与resultMap
resutType
我们上述的例子中查询操作都使用的是resultType,它是默认让数据库字段名与实体类属性名做了映射,也就是字段名与属性名必须对应起来,如果某个属性和字段没有对应,那么该字段的值就不会映射到属性上,导致该属性的值是一个null

如果字段与属性不对应的话,还想继续使用resultType的话,我们在可以使用as对字段起别名

resultMap
有时候程序中对象的属性和数据库的字段对应不起来,我们可以使用resultMap做映射
如下所示,user的属性pwd与数据库的password对应不起来,此时可以使用resultMap


6.2 单表查询
前面涉及到的查询都是单表查询,此处不过多介绍
6.3 多表查询
现在有两张表,user和article

我们现在如果要查询某篇文章,并且要查询出它的用户信息,也就是uid对应到user中id的用户的用户名(username)信息,此时发现我们的两个实体类User,Article都不能对应完成此次查询的所有信息

此时我们可以创建一个
包vo,在此包下创建一个类ArticleVO,此类继承Article,并且添加一个属性username,此时该类就可完成我们此处的多表查询操作

@Data
public class ArticleVO extends Article {
private String username;
@Override
public String toString() {
return "ArticleVO{" +
"username='" + username + '\'' +
'}'+super.toString();
}
}
加了@Data注解为什么还要重写toString()?
因为@Data注解的toString方法只给该类的一个属性username输入打印,但是此类继承了Article,我们希望toString可以可以打印出继承的父类属性信息
创建ArticleMapper接口及ArticleMapper.xml文件

编写测试类及测试方法

启动观察结果:获取到我们想要的数据

7. 动态SQL
动态sql是MyBatis的强大特性之一,它可以完成不同条件下不同的sql拼接,它是在xml文件中进行不同条件拼接的
7.1 <if>标签
比如我们插入用户信息,有username(用户名),password(密码),photo(图像),但是当传递的用户信息中photo为null时,我们就不插入photo信息,如果用户信息中photo不为null,我们就插入photo信息
photo信息为null

说明: if标签中test属性中的photo为传递对象的属性而不是数据库表的字段
测试方法及打印结果:

photo信息不为null


7.2 <trim>标签
上述的<if>标签如果使用了多个,如下面这种情况

为了解决上述问题,我们在整个需要判断条件的语块中添加<trim>标签
关于<trim>标签的属性:
prefix:表示整个语句块,以prefix的值作为前缀suffix:表示整个语句块,以suffix的值作为后缀prefixOverrides:表示整个语句块要去除的前缀suffixOverrides:表示整个语句块要去除的后缀
注意: 当trim标签里没有任何内容的时候,是不会加上前缀后缀的,只有trim里面有内容,前缀和后缀的设置才生效
我们可以调整上述xml中的sql为:

测试方法:

打印结果:

7.3 <where>标签
当传入对象,根据属性进行where条件筛选的时候,当属性不为null的都为查询条件
当<where>标签中有内容时候,此时sql会加上where进行条件筛选,如果无内容则不会添加where,并且<where>标签会去除前缀and(不能去掉后缀and)

7.4 <set>标签
根据传入的对象属性来更新数据,如果<set>标签里没内容,则不生成set,当内容不为空的时候会生成set,并且<set>标签会自动去除后缀 ,(不会去除前缀)
<update id="updateById">
update user
<set>
<if test="username != null">
username=#{username}
</if>
<if test="password != null">
password=#{password}
</if>
</set>
<where>
<if test="id != null">
id=#{id}
</if>
</where>
</update>
7.5 <foreach>标签
对某个集合遍历可以使用该标签,该标签有如下属性:
collection:绑定方法中的集合对象item:遍历时,集合中的每一个元素open:给语句块添加前缀close:给语句块添加后缀separator:间隔字符串
示例:我们删除id为6,7,8,9的用户

测试方法:
@Test
void deleteByIds() {
int[] ids = {6,7,8,9};
int n = userMapper.deleteByIds(ids);
System.out.println(n);
}
打印结果:









![[架构之路-197]-《软考-系统分析师》- 关键技术 - 问题分析阶段重要的四个任务](https://img-blog.csdnimg.cn/img_convert/009a8bb7b0c7e76a1d50ac128c85da4e.jpeg)










