Files
java-book/3.javaEE/day09/Mybatis.md
2025-08-27 14:54:36 +08:00

1489 lines
39 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 1. Mybatis基础操作
学习完mybatis入门后我们继续学习mybatis基础操作。
## 1.1 需求
需求说明:
- 根据资料中提供的《tlias智能学习辅助系统》页面原型及需求完成员工管理的需求开发。
![image-20221210180155700](assets/image-20221210180155700.png)
![image-20221210180343288](assets/image-20221210180343288.png)
![image-20221210180515206](assets/image-20221210180515206.png)
通过分析以上的页面原型和需求,我们确定了功能列表:
1. 查询
- 根据主键ID查询
- 条件查询
2. 新增
3. 更新
4. 删除
- 根据主键ID删除
- 根据主键ID批量删除
## 1.2 准备
实施前的准备工作:
1. 准备数据库表
2. 创建一个新的springboot工程选择引入对应的起步依赖mybatis、mysql驱动、lombok
3. application.properties中引入数据库连接信息
4. 创建对应的实体类 Emp实体类属性采用驼峰命名
5. 准备Mapper接口 EmpMapper
**准备数据库表**
~~~mysql
-- 部门管理
create table dept
(
id int unsigned primary key auto_increment comment '主键ID',
name varchar(10) not null unique comment '部门名称',
create_time datetime not null comment '创建时间',
update_time datetime not null comment '修改时间'
) comment '部门表';
-- 部门表测试数据
insert into dept (id, name, create_time, update_time)
values (1, '学工部', now(), now()),
(2, '教研部', now(), now()),
(3, '咨询部', now(), now()),
(4, '就业部', now(), now()),
(5, '人事部', now(), now());
-- 员工管理
create table emp
(
id int unsigned primary key auto_increment comment 'ID',
username varchar(20) not null unique comment '用户名',
password varchar(32) default '123456' comment '密码',
name varchar(10) not null comment '姓名',
gender tinyint unsigned not null comment '性别, 说明: 1 男, 2 女',
image varchar(300) comment '图像',
job tinyint unsigned comment '职位, 说明: 1 班主任,2 讲师, 3 学工主管, 4 教研主管, 5 咨询师',
entrydate date comment '入职时间',
dept_id int unsigned comment '部门ID',
create_time datetime not null comment '创建时间',
update_time datetime not null comment '修改时间'
) comment '员工表';
-- 员工表测试数据
INSERT INTO emp (id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time)
VALUES
(1, 'jinyong', '123456', '金庸', 1, '1.jpg', 4, '2000-01-01', 2, now(), now()),
(2, 'zhangwuji', '123456', '张无忌', 1, '2.jpg', 2, '2015-01-01', 2, now(), now()),
(3, 'yangxiao', '123456', '杨逍', 1, '3.jpg', 2, '2008-05-01', 2, now(), now()),
(4, 'weiyixiao', '123456', '韦一笑', 1, '4.jpg', 2, '2007-01-01', 2, now(), now()),
(5, 'changyuchun', '123456', '常遇春', 1, '5.jpg', 2, '2012-12-05', 2, now(), now()),
(6, 'xiaozhao', '123456', '小昭', 2, '6.jpg', 3, '2013-09-05', 1, now(), now()),
(7, 'jixiaofu', '123456', '纪晓芙', 2, '7.jpg', 1, '2005-08-01', 1, now(), now()),
(8, 'zhouzhiruo', '123456', '周芷若', 2, '8.jpg', 1, '2014-11-09', 1, now(), now()),
(9, 'dingminjun', '123456', '丁敏君', 2, '9.jpg', 1, '2011-03-11', 1, now(), now()),
(10, 'zhaomin', '123456', '赵敏', 2, '10.jpg', 1, '2013-09-05', 1, now(), now()),
(11, 'luzhangke', '123456', '鹿杖客', 1, '11.jpg', 5, '2007-02-01', 3, now(), now()),
(12, 'hebiweng', '123456', '鹤笔翁', 1, '12.jpg', 5, '2008-08-18', 3, now(), now()),
(13, 'fangdongbai', '123456', '方东白', 1, '13.jpg', 5, '2012-11-01', 3, now(), now()),
(14, 'zhangsanfeng', '123456', '张三丰', 1, '14.jpg', 2, '2002-08-01', 2, now(), now()),
(15, 'yulianzhou', '123456', '俞莲舟', 1, '15.jpg', 2, '2011-05-01', 2, now(), now()),
(16, 'songyuanqiao', '123456', '宋远桥', 1, '16.jpg', 2, '2010-01-01', 2, now(), now()),
(17, 'chenyouliang', '123456', '陈友谅', 1, '17.jpg', NULL, '2015-03-21', NULL, now(), now());
~~~
**创建一个新的springboot工程选择引入对应的起步依赖mybatis、mysql驱动、lombok**
![image-20221210182008131](assets/image-20221210182008131.png)
**application.properties中引入数据库连接信息**
> 提示:可以把之前项目中已有的配置信息复制过来即可
~~~properties
#驱动类名称
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#数据库连接的url
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis
#连接数据库的用户名
spring.datasource.username=root
#连接数据库的密码
spring.datasource.password=1234
~~~
**创建对应的实体类Emp实体类属性采用驼峰命名**
~~~java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Emp {
private Integer id;
private String username;
private String password;
private String name;
private Short gender;
private String image;
private Short job;
private LocalDate entrydate; //LocalDate类型对应数据表中的date类型
private Integer deptId;
private LocalDateTime createTime;//LocalDateTime类型对应数据表中的datetime类型
private LocalDateTime updateTime;
}
~~~
**准备Mapper接口EmpMapper**
~~~java
/*@Mapper注解表示当前接口为mybatis中的Mapper接口
程序运行时会自动创建接口的实现类对象(代理对象)并交给Spring的IOC容器管理
*/
@Mapper
public interface EmpMapper {
}
~~~
完成以上操作后,项目工程结构目录如下:
![image-20250621171125313](Mybatis/image-20250621171125313.png)
## 1.3 删除
### 1.3.1 功能实现
页面原型:
![image-20221210183336095](assets/image-20221210183336095.png)
> 当我们点击后面的"删除"按钮时前端页面会给服务端传递一个参数也就是该行数据的ID。 我们接收到ID后根据ID删除数据即可。
**功能:根据主键删除数据**
- SQL语句
~~~mysql
-- 删除id=17的数据
delete from emp where id = 17;
~~~
> Mybatis框架让程序员更关注于SQL语句
- 接口方法
~~~java
@Mapper
public interface EmpMapper {
//@Delete("delete from emp where id = 17")
//public void delete();
//以上delete操作的SQL语句中的id值写成固定的17就表示只能删除id=17的用户数据
//SQL语句中的id值不能写成固定数值需要变为动态的数值
//解决方案在delete方法中添加一个参数(用户id)将方法中的参数传给SQL语句
/**
* 根据id删除数据
* @param id 用户id
*/
@Delete("delete from emp where id = #{id}")//使用#{key}方式获取方法中的参数值
public void delete(Integer id);
}
~~~
> @Delete注解用于编写delete操作的SQL语句
> 如果mapper接口方法形参只有一个普通类型的参数#{…} 里面的属性名可以随便写,如:#{id}、#{value}。但是建议保持名字一致。
- 测试
- 在单元测试类中通过@Autowired注解注入EmpMapper类型对象
~~~java
@SpringBootTest
class SpringbootMybatisCrudApplicationTests {
@Autowired //从Spring的IOC容器中获取类型是EmpMapper的对象并注入
private EmpMapper empMapper;
@Test
public void testDel(){
//调用删除方法
empMapper.delete(16);
}
}
~~~
### 1.3.2 日志输入
在Mybatis当中我们可以借助日志查看到sql语句的执行、执行传递的参数以及执行结果。具体操作如下
1. 打开application.properties文件
2. 开启mybatis的日志并指定输出到控制台
```properties
#指定mybatis输出日志的位置, 输出控制台
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
```
开启日志之后我们再次运行单元测试可以看到在控制台中输出了以下的SQL语句信息
![image-20220901164225644](assets/image-20220901164225644.png)
> 但是我们发现输出的SQL语句delete from emp where id = ?我们输入的参数16并没有在后面拼接id的值是使用?进行占位。那这种SQL语句我们称为预编译SQL。
### 1.3.3 预编译SQL
#### 1.3.3.1 介绍
预编译SQL有两个优势
1. 性能更高
2. 更安全(防止SQL注入)
![image-20221210202222206](assets/image-20221210202222206.png)
> 性能更高预编译SQL编译一次之后会将编译后的SQL语句缓存起来后面再次执行这条语句时不会再次编译。只是输入的参数不同
>
> 更安全(防止SQL注入)将敏感字进行转义保障SQL的安全性。
#### 1.3.3.2 SQL注入
SQL注入是通过操作输入的数据来修改事先定义好的SQL语句以达到执行代码对服务器进行攻击的方法。
> 由于没有对用户输入进行充分检查而SQL又是拼接而成在用户输入参数时在参数中添加一些SQL关键字达到改变SQL运行结果的目的也可以完成恶意攻击。
**测试1使用资料中提供的程序来验证SQL注入问题**
![image-20221210205419634](assets/image-20221210205419634.png)
第1步进入到DOS
![image-20221211124744203](assets/image-20221211124744203.png)
![image-20221211124840720](assets/image-20221211124840720.png)
第2步执行以下命令启动程序
~~~powershell
#启动存在SQL注入的程序
java -jar sql_Injection_demo-0.0.1-SNAPSHOT.jar
~~~
![image-20221210211605231](assets/image-20221210211605231.png)
第3步打开浏览器输入`http://localhost:9090/login.html`
![image-20221210212406527](assets/image-20221210212406527.png)
发现竟然能够登录成功:
![image-20221210212511915](assets/image-20221210212511915.png)
以上操作为什么能够登录成功呢?
- 由于没有对用户输入内容进行充分检查而SQL又是字符串拼接方式而成在用户输入参数时在参数中添加一些SQL关键字达到改变SQL运行结果的目的从而完成恶意攻击。
![image-20221210213311518](assets/image-20221210213311518.png)
> ![image-20221210214431228](assets/image-20221210214431228.png)
>
> 用户在页面提交数据的时候人为的添加一些特殊字符使得sql语句的结构发生了变化最终可以在没有用户名或者密码的情况下进行登录。
**测试2使用资料中提供的程序来验证SQL注入问题**
第1步进入到DOS
第2步执行以下命令启动程序
~~~powershell
#启动解决了SQL注入的程序
java -jar sql_prepared_demo-0.0.1-SNAPSHOT.jar
~~~
第3步打开浏览器输入`http://localhost:9090/login.html`
![image-20221210212406527](assets/image-20221210212406527.png)
发现无法登录:
![image-20221211125751981](assets/image-20221211125751981.png)
以上操作SQL语句的执行
![image-20221211130011973](assets/image-20221211130011973.png)
> 把整个`' or '1'='1`作为一个完整的参数赋值给第2个问号`' or '1'='1`进行了转义,只当做字符串使用)
#### 1.3.3.3 参数占位符
在Mybatis中提供的参数占位符有两种${...} 、#{...}
- #{...}
- 执行SQL时会将#{…}替换为?生成预编译SQL会自动设置参数值
- 使用时机:参数传递,都使用#{…}
- ${...}
- 拼接SQL。直接将参数拼接在SQL语句中存在SQL注入问题
- 使用时机:如果对表名、列表进行动态设置时使用
> 注意事项:在项目开发中,建议使用#{...}生成预编译SQL防止SQL注入安全。
## 1.4 新增
功能:新增员工信息
![image-20221211134239610](assets/image-20221211134239610.png)
### 1.4.1 基本新增
员工表结构:
![image-20221211134746319](assets/image-20221211134746319.png)
SQL语句
```sql
insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time) values ('songyuanqiao','宋远桥',1,'1.jpg',2,'2012-10-09',2,'2022-10-01 10:00:00','2022-10-01 10:00:00');
```
接口方法:
```java
@Mapper
public interface EmpMapper {
@Insert("insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time) values (#{username}, #{name}, #{gender}, #{image}, #{job}, #{entrydate}, #{deptId}, #{createTime}, #{updateTime})")
public void insert(Emp emp);
}
```
> 说明:#{...} 里面写的名称是对象的属性名
测试类:
```java
import com.inmind.mapper.EmpMapper;
import com.inmind.pojo.Emp;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.time.LocalDate;
import java.time.LocalDateTime;
@SpringBootTest
class SpringbootMybatisCrudApplicationTests {
@Autowired
private EmpMapper empMapper;
@Test
public void testInsert(){
//创建员工对象
Emp emp = new Emp();
emp.setUsername("tom");
emp.setName("汤姆");
emp.setImage("1.jpg");
emp.setGender((short)1);
emp.setJob((short)1);
emp.setEntrydate(LocalDate.of(2000,1,1));
emp.setCreateTime(LocalDateTime.now());
emp.setUpdateTime(LocalDateTime.now());
emp.setDeptId(1);
//调用添加方法
empMapper.insert(emp);
}
}
```
> 日志输出:
>
> ![image-20221211140222240](assets/image-20221211140222240.png)
### 1.4.2 主键返回
概念:在数据添加成功后,需要获取插入数据库数据的主键。
>
那要如何实现在插入数据之后返回所插入行的主键值呢?
- 默认情况下执行插入操作时是不会主键值返回的。如果我们想要拿到主键值需要在Mapper接口中的方法上添加一个Options注解并在注解中指定属性useGeneratedKeys=true和keyProperty="实体类属性名"
主键返回代码实现:
~~~java
@Mapper
public interface EmpMapper {
//会自动将生成的主键值赋值给emp对象的id属性
@Options(useGeneratedKeys = true,keyProperty = "id")
@Insert("insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time) values (#{username}, #{name}, #{gender}, #{image}, #{job}, #{entrydate}, #{deptId}, #{createTime}, #{updateTime})")
public void insert(Emp emp);
}
~~~
测试:
~~~java
@SpringBootTest
class SpringbootMybatisCrudApplicationTests {
@Autowired
private EmpMapper empMapper;
@Test
public void testInsert(){
//创建员工对象
Emp emp = new Emp();
emp.setUsername("jack");
emp.setName("杰克");
emp.setImage("1.jpg");
emp.setGender((short)1);
emp.setJob((short)1);
emp.setEntrydate(LocalDate.of(2000,1,1));
emp.setCreateTime(LocalDateTime.now());
emp.setUpdateTime(LocalDateTime.now());
emp.setDeptId(1);
//调用添加方法
empMapper.insert(emp);
System.out.println(emp.getDeptId());
}
}
~~~
## 1.5 更新
功能:修改员工信息
![image-20221212095605863](assets/image-20221212095605863.png)
> 点击"编辑"按钮后,会查询所在行记录的员工信息,并把员工信息回显在修改员工的窗体上(下个知识点学习)
>
> 在修改员工的窗体上,可以修改的员工数据:用户名、员工姓名、性别、图像、职位、入职日期、归属部门
>
> 思考:在修改员工数据时,要以什么做为条件呢?
>
> 答案员工id
SQL语句
```sql
update emp set username = 'linghushaoxia', name = '令狐少侠', gender = 1 , image = '1.jpg' , job = 2, entrydate = '2012-01-01', dept_id = 2, update_time = '2022-10-01 12:12:12' where id = 18;
```
接口方法:
```java
@Mapper
public interface EmpMapper {
/**
* 根据id修改员工信息
* @param emp
*/
@Update("update emp set username=#{username}, name=#{name}, gender=#{gender}, image=#{image}, job=#{job}, entrydate=#{entrydate}, dept_id=#{deptId}, update_time=#{updateTime} where id=#{id}")
public void update(Emp emp);
}
```
测试类:
```java
@SpringBootTest
class SpringbootMybatisCrudApplicationTests {
@Autowired
private EmpMapper empMapper;
@Test
public void testUpdate(){
//要修改的员工信息
Emp emp = new Emp();
emp.setId(23);
emp.setUsername("songdaxia");
emp.setPassword(null);
emp.setName("老宋");
emp.setImage("2.jpg");
emp.setGender((short)1);
emp.setJob((short)2);
emp.setEntrydate(LocalDate.of(2012,1,1));
emp.setCreateTime(null);
emp.setUpdateTime(LocalDateTime.now());
emp.setDeptId(2);
//调用方法,修改员工数据
empMapper.update(emp);
}
}
```
## 1.6 查询
### 1.6.1 根据ID查询
在员工管理的页面中,当我们进行更新数据时,会点击 “编辑” 按钮然后此时会发送一个请求到服务端会根据Id查询该员工信息并将员工数据回显在页面上。
![image-20221212101331292](assets/image-20221212101331292.png)
SQL语句
~~~mysql
select id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time from emp;
~~~
接口方法:
~~~java
@Mapper
public interface EmpMapper {
@Select("select id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time from emp where id=#{id}")
public Emp getById(Integer id);
}
~~~
测试类:
~~~java
@SpringBootTest
class SpringbootMybatisCrudApplicationTests {
@Autowired
private EmpMapper empMapper;
@Test
public void testGetById(){
Emp emp = empMapper.getById(1);
System.out.println(emp);
}
}
~~~
> 执行结果:
>
> ![image-20221212103004961](assets/image-20221212103004961.png)
>
> 而在测试的过程中,我们会发现有几个字段(deptId、createTime、updateTime)是没有数据值的
### 1.6.2 数据封装
我们看到查询返回的结果中大部分字段是有值的但是deptIdcreateTimeupdateTime这几个字段是没有值的而数据库中是有对应的字段值的这是为什么呢
![image-20221212103124490](assets/image-20221212103124490.png)
原因如下:
- 实体类属性名和数据库表查询返回的字段名一致mybatis会自动封装。
- 如果实体类属性名和数据库表查询返回的字段名不一致,不能自动封装。
解决方案:
1. 起别名
2. 结果映射
3. 开启驼峰命名
**起别名**在SQL语句中对不一样的列名起别名别名和实体类属性名一样
```java
@Select("select id, username, password, name, gender, image, job, entrydate, " +
"dept_id AS deptId, create_time AS createTime, update_time AS updateTime " +
"from emp " +
"where id=#{id}")
public Emp getById(Integer id);
```
> 再次执行测试类:
>
> ![image-20221212111027396](assets/image-20221212111027396.png)
**手动结果映射**:通过 @Results及@Result 进行手动结果映射
```java
@Results({@Result(column = "dept_id", property = "deptId"),
@Result(column = "create_time", property = "createTime"),
@Result(column = "update_time", property = "updateTime")})
@Select("select id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time from emp where id=#{id}")
public Emp getById(Integer id);
```
> @Results源代码
>
> ~~~java
> @Documented
> @Retention(RetentionPolicy.RUNTIME)
> @Target({ElementType.METHOD})
> public @interface Results {
> String id() default "";
>
> Result[] value() default {}; //Result类型的数组
> }
> ~~~
>
> @Result源代码
>
> ~~~java
> @Documented
> @Retention(RetentionPolicy.RUNTIME)
> @Target({ElementType.METHOD})
> @Repeatable(Results.class)
> public @interface Result {
> boolean id() default false;//表示当前列是否为主键true:是主键)
>
> String column() default "";//指定表中字段名
>
> String property() default "";//指定类中属性名
>
> Class<?> javaType() default void.class;
>
> JdbcType jdbcType() default JdbcType.UNDEFINED;
>
> Class<? extends TypeHandler> typeHandler() default UnknownTypeHandler.class;
>
> One one() default @One;
>
> Many many() default @Many;
> }
> ~~~
**开启驼峰命名(推荐)**如果字段名与属性名符合驼峰命名规则mybatis会自动通过驼峰命名规则映射
> 驼峰命名规则: abc_xyz => abcXyz
>
> - 表中字段名abc_xyz
> - 类中属性名abcXyz
```properties
# 在application.properties中添加
mybatis.configuration.map-underscore-to-camel-case=true
```
> 要使用驼峰命名前提是 实体类的属性 与 数据库表中的字段名严格遵守驼峰命名。
### 1.6.3 条件查询
在员工管理的列表页面中,我们需要根据条件查询员工信息,查询条件包括:姓名、性别、入职时间。
![image-20221212113422924](assets/image-20221212113422924.png)
通过页面原型以及需求描述我们要实现的查询:
- 姓名:要求支持模糊匹配
- 性别:要求精确匹配
- 入职时间:要求进行范围查询
- 根据最后修改时间进行降序排序
SQL语句
```sql
select id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time
from emp
where name like '%张%'
and gender = 1
and entrydate between '2010-01-01' and '2020-01-01 '
order by update_time desc;
```
接口方法:
- 方式一
```java
@Mapper
public interface EmpMapper {
@Select("select * from emp " +
"where name like '%${name}%' " +
"and gender = #{gender} " +
"and entrydate between #{begin} and #{end} " +
"order by update_time desc")
public List<Emp> list(String name, Short gender, LocalDate begin, LocalDate end);
}
```
> ![image-20221212115149151](assets/image-20221212115149151.png)
>
> 以上方式注意事项:
>
> 1. 方法中的形参名和SQL语句中的参数占位符名保持一致
>
> 2. 模糊查询使用$&#123;...}进行字符串拼接这种方式呢由于是字符串拼接并不是预编译的形式所以效率不高、且存在sql注入风险。
- 方式二解决SQL注入风险
- 使用MySQL提供的字符串拼接函数concat('%' , '关键字' , '%')
~~~java
@Mapper
public interface EmpMapper {
@Select("select * from emp " +
"where name like concat('%',#{name},'%') " +
"and gender = #{gender} " +
"and entrydate between #{begin} and #{end} " +
"order by update_time desc")
public List<Emp> list(String name, Short gender, LocalDate begin, LocalDate end);
}
~~~
> 执行结果生成的SQL都是预编译的SQL语句性能高、安全
>
> ![image-20221212120006242](assets/image-20221212120006242.png)
### 1.6.4 参数名说明
在上面我们所编写的条件查询功能中我们需要保证接口中方法的形参名和SQL语句中的参数占位符名相同。
> 当方法中的形参名和SQL语句中的占位符参数名不相同时就会出现以下问题
>
> ![image-20221212150611796](assets/image-20221212150611796.png)
参数名在不同的SpringBoot版本中处理方案还不同
- 在springBoot的2.x版本保证参数名一致
![image-20221212151156273](assets/image-20221212151156273.png)
> springBoot的父工程对compiler编译插件进行了默认的参数parameters配置使得在编译时会在生成的字节码文件中保留原方法形参的名称所以#&#123;…}里面可以直接通过形参名获取对应的值
>
> ![image-20221212151411154](assets/image-20221212151411154.png)
- 在springBoot的1.x版本/单独使用mybatis使用@Param注解来指定SQL语句中的参数名
![image-20221212151628715](assets/image-20221212151628715.png)
> 在编译时生成的字节码文件当中不会保留Mapper接口中方法的形参名称而是使用var1、var2、...这样的形参名字,此时要获取参数值时,就要通过@Param注解来指定SQL语句中的参数名
>
> ![image-20221212151736274](assets/image-20221212151736274.png)
# 2. Mybatis的XML配置文件
Mybatis的开发有两种方式
1. 注解
2. XML
## 2.1 XML配置文件规范
使用Mybatis的注解方式主要是来完成一些简单的增删改查功能。如果需要实现复杂的SQL功能建议使用XML来配置映射语句也就是将SQL语句写在XML配置文件中。
在Mybatis中使用XML映射文件方式开发需要符合一定的规范
1. XML映射文件的名称与Mapper接口名称一致并且将XML映射文件和Mapper接口放置在相同包下同包同名
2. XML映射文件的namespace属性为Mapper接口全限定名一致
3. XML映射文件中sql语句的id与Mapper接口中的方法名一致并保持返回类型一致。
![image-20221212153529732](assets/image-20221212153529732.png)
> \&lt;select>标签就是用于编写select查询语句的。
>
> - resultType属性指的是查询返回的单条记录所封装的类型。
## 2.2 XML配置文件实现
第1步创建XML映射文件
![image-20221212154908306](assets/image-20221212154908306.png)
![image-20221212155304635](assets/image-20221212155304635.png)
![image-20221212155544404](assets/image-20221212155544404.png)
第2步编写XML映射文件
> xml映射文件中的dtd约束直接从mybatis官网复制即可
~~~xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="">
</mapper>
~~~
配置XML映射文件的namespace属性为Mapper接口全限定名
![image-20221212160316644](assets/image-20221212160316644.png)
~~~xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.inmind.mapper.EmpMapper">
</mapper>
~~~
配置XML映射文件中sql语句的id与Mapper接口中的方法名一致并保持返回类型一致
![image-20221212163528787](assets/image-20221212163528787.png)
~~~xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.inmind.mapper.EmpMapper">
<!--查询操作-->
<select id="list" resultType="com.inmind.pojo.Emp">
select * from emp
where name like concat('%',#{name},'%')
and gender = #{gender}
and entrydate between #{begin} and #{end}
order by update_time desc
</select>
</mapper>
~~~
> 运行测试类,执行结果:
>
> ![image-20221212163719534](assets/image-20221212163719534.png)
## 2.3 MybatisX的使用
MybatisX是一款基于IDEA的快速开发Mybatis的插件为效率而生。
MybatisX的安装
![image-20221213120923252](assets/image-20221213120923252.png)
可以通过MybatisX快速定位
![image-20221213121521406](assets/image-20221213121521406.png)
> MybatisX的使用在后续学习中会继续分享
学习了Mybatis中XML配置文件的开发方式了大家可能会存在一个疑问到底是使用注解方式开发还是使用XML方式开发
> 官方说明https://mybatis.net.cn/getting-started.html
>
> ![image-20220901173948645](assets/image-20220901173948645.png)
**结论:**使用Mybatis的注解主要是来完成一些简单的增删改查功能。如果需要实现复杂的SQL功能建议使用XML来配置映射语句。
# 3. Mybatis动态SQL
## 3.1 什么是动态SQL
在页面原型中列表上方的条件是动态的是可以不传递的也可以只传递其中的1个或者2个或者全部。
![](assets/image-20220901172933012.png)
![image-20220901173203491](assets/image-20220901173203491.png)
而在我们刚才编写的SQL语句中我们会看到我们将三个条件直接写死了。 如果页面只传递了参数姓名name 字段,其他两个字段 性别 和 入职时间没有传递那么这两个参数的值就是null。
此时执行的SQL语句为
![image-20220901173431554](assets/image-20220901173431554.png)
这个查询结果是不正确的。正确的做法应该是:传递了参数,再组装这个查询条件;如果没有传递参数,就不应该组装这个查询条件。
比如:如果姓名输入了"张", 对应的SQL为:
```sql
select * from emp where name like '%张%' order by update_time desc;
```
如果姓名输入了"张",,性别选择了"男"则对应的SQL为:
```sql
select * from emp where name like '%张%' and gender = 1 order by update_time desc;
```
SQL语句会随着用户的输入或外部条件的变化而变化我们称为**动态SQL**。
![image-20221213122623278](assets/image-20221213122623278.png)
在Mybatis中提供了很多实现动态SQL的标签我们学习Mybatis中的动态SQL就是掌握这些动态SQL标签。
## 3.2 动态SQL-if
`&lt;if>`用于判断条件是否成立。使用test属性进行条件判断如果条件为true则拼接SQL。
~~~xml
<if test="条件表达式">
要拼接的sql语句
</if>
~~~
接下来,我们就通过`&lt;if>`标签来改造之前条件查询的案例。
### 3.2.1 条件查询
示例把SQL语句改造为动态SQL方式
- 原有的SQL语句
~~~xml
<select id="list" resultType="com.inmind.pojo.Emp">
select * from emp
where name like concat('%',#{name},'%')
and gender = #{gender}
and entrydate between #{begin} and #{end}
order by update_time desc
</select>
~~~
- 动态SQL语句
~~~xml
<select id="list" resultType="com.inmind.pojo.Emp">
select * from emp
where
<if test="name != null">
name like concat('%',#{name},'%')
</if>
<if test="gender != null">
and gender = #{gender}
</if>
<if test="begin != null and end != null">
and entrydate between #{begin} and #{end}
</if>
order by update_time desc
</select>
~~~
测试方法:
~~~java
@Test
public void testList(){
//性别数据为null、开始时间和结束时间也为null
List<Emp> list = empMapper.list("张", null, null, null);
for(Emp emp : list){
System.out.println(emp);
}
}
~~~
> 执行的SQL语句
>
> ![image-20221213140353285](assets/image-20221213140353285.png)
下面呢,我们修改测试方法中的代码,再次进行测试,观察执行情况:
~~~java
@Test
public void testList(){
//姓名为null
List<Emp> list = empMapper.list(null, (short)1, null, null);
for(Emp emp : list){
System.out.println(emp);
}
}
~~~
执行结果:
![image-20221213141139015](assets/image-20221213141139015.png)
![image-20221213141253355](assets/image-20221213141253355.png)
再次修改测试方法中的代码,再次进行测试:
~~~java
@Test
public void testList(){
//传递的数据全部为null
List<Emp> list = empMapper.list(null, null, null, null);
for(Emp emp : list){
System.out.println(emp);
}
}
~~~
执行的SQL语句
![image-20221213143854434](assets/image-20221213143854434.png)
以上问题的解决方案:使用`&lt;where>`标签代替SQL语句中的where关键字
- `&lt;where>`只会在子元素有内容的情况下才插入where子句而且会自动去除子句的开头的AND或OR
~~~xml
<select id="list" resultType="com.inmind.pojo.Emp">
select * from emp
<where>
<!-- if做为where标签的子元素 -->
<if test="name != null">
and name like concat('%',#{name},'%')
</if>
<if test="gender != null">
and gender = #{gender}
</if>
<if test="begin != null and end != null">
and entrydate between #{begin} and #{end}
</if>
</where>
order by update_time desc
</select>
~~~
测试方法:
~~~java
@Test
public void testList(){
//只有性别
List<Emp> list = empMapper.list(null, (short)1, null, null);
for(Emp emp : list){
System.out.println(emp);
}
}
~~~
> 执行的SQL语句
>
> ![image-20221213141909455](assets/image-20221213141909455.png)
### 3.2.2 更新员工
案例:完善更新员工功能,修改为动态更新员工数据信息
- 动态更新员工信息,如果更新时传递有值,则更新;如果更新时没有传递值,则不更新
- 解决方案动态SQL
修改Mapper接口
~~~java
@Mapper
public interface EmpMapper {
//删除@Update注解编写的SQL语句
//update操作的SQL语句编写在Mapper映射文件中
public void update(Emp emp);
}
~~~
修改Mapper映射文件
~~~xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.inmind.mapper.EmpMapper">
<!--更新操作-->
<update id="update">
update emp
set
<if test="username != null">
username=#{username},
</if>
<if test="name != null">
name=#{name},
</if>
<if test="gender != null">
gender=#{gender},
</if>
<if test="image != null">
image=#{image},
</if>
<if test="job != null">
job=#{job},
</if>
<if test="entrydate != null">
entrydate=#{entrydate},
</if>
<if test="deptId != null">
dept_id=#{deptId},
</if>
<if test="updateTime != null">
update_time=#{updateTime}
</if>
where id=#{id}
</update>
</mapper>
~~~
测试方法:
~~~java
@Test
public void testUpdate2(){
//要修改的员工信息
Emp emp = new Emp();
emp.setId(20);
emp.setUsername("Tom111");
emp.setName("汤姆111");
emp.setUpdateTime(LocalDateTime.now());
//调用方法,修改员工数据
empMapper.update(emp);
}
~~~
> 执行的SQL语句
>
> ![image-20221213152533851](assets/image-20221213152533851.png)
再次修改测试方法观察SQL语句执行情况
~~~java
@Test
public void testUpdate2(){
//要修改的员工信息
Emp emp = new Emp();
emp.setId(20);
emp.setUsername("Tom222");
//调用方法,修改员工数据
empMapper.update(emp);
}
~~~
> 执行的SQL语句
>
> ![image-20221213152850322](assets/image-20221213152850322.png)
以上问题的解决方案:使用`&lt;set>`标签代替SQL语句中的set关键字
- `&lt;set>`动态的在SQL语句中插入set关键字并会删掉额外的逗号。用于update语句中
~~~xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.inmind.mapper.EmpMapper">
<!--更新操作-->
<update id="update">
update emp
<!-- 使用set标签代替update语句中的set关键字 -->
<set>
<if test="username != null">
username=#{username},
</if>
<if test="name != null">
name=#{name},
</if>
<if test="gender != null">
gender=#{gender},
</if>
<if test="image != null">
image=#{image},
</if>
<if test="job != null">
job=#{job},
</if>
<if test="entrydate != null">
entrydate=#{entrydate},
</if>
<if test="deptId != null">
dept_id=#{deptId},
</if>
<if test="updateTime != null">
update_time=#{updateTime}
</if>
</set>
where id=#{id}
</update>
</mapper>
~~~
> 再次执行测试方法执行的SQL语句
>
> ![image-20221213153329553](assets/image-20221213153329553.png)
**小结**
- `&lt;if>`
- 用于判断条件是否成立如果条件为true则拼接SQL
- 形式:
~~~xml
<if test="name != null"></if>
~~~
- `&lt;where>`
- where元素只会在子元素有内容的情况下才插入where子句而且会自动去除子句的开头的AND或OR
- `&lt;set>`
- 动态地在行首插入 SET 关键字并会删掉额外的逗号。用在update语句中
## 3.3 动态SQL-foreach
案例:员工删除功能(既支持删除单条记录,又支持批量删除)
![image-20220901181751004](assets/image-20220901181751004.png)
SQL语句
~~~mysql
delete from emp where id in (1,2,3);
~~~
Mapper接口
~~~java
@Mapper
public interface EmpMapper {
//批量删除
public void deleteByIds(List<Integer> ids);
}
~~~
XML映射文件
- 使用`&lt;foreach>`遍历deleteByIds方法中传递的参数ids集合
~~~xml
<foreach collection="集合名称" item="集合遍历出来的元素/项" separator="每一次遍历使用的分隔符"
open="遍历开始前拼接的片段" close="遍历结束后拼接的片段">
</foreach>
~~~
~~~xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.inmind.mapper.EmpMapper">
<!--删除操作-->
<delete id="deleteByIds">
delete from emp where id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>
</mapper>
~~~
> ![image-20221213165710141](assets/image-20221213165710141.png)
> 执行的SQL语句
>
> ![image-20221213164957636](assets/image-20221213164957636.png)
## 3.4 动态SQL-sql&include
问题分析:
- 在xml映射文件中配置的SQL有时可能会存在很多重复的片段此时就会存在很多冗余的代码
![](assets/image-20220901182204358.png)
![](assets/image-20220901182249421.png)
我们可以对重复的代码片段进行抽取,将其通过`&lt;sql>`标签封装到一个SQL片段然后再通过`&lt;include>`标签进行引用。
- `&lt;sql>`定义可重用的SQL片段
- `&lt;include>`通过属性refid指定包含的SQL片段
![image-20221213171244796](assets/image-20221213171244796.png)
SQL片段 抽取重复的代码
```xml
<sql id="commonSelect">
select id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time from emp
</sql>
```
然后通过`&lt;include>` 标签在原来抽取的地方进行引用。操作如下:
```xml
<select id="list" resultType="com.inmind.pojo.Emp">
<include refid="commonSelect"/>
<where>
<if test="name != null">
name like concat('%',#{name},'%')
</if>
<if test="gender != null">
and gender = #{gender}
</if>
<if test="begin != null and end != null">
and entrydate between #{begin} and #{end}
</if>
</where>
order by update_time desc
</select>
```