MyBatis 实战指南:探索灵活持久化的艺术
文章目录
- 前言
- 一、初识 MyBatis
- 1.1 什么是 MyBatis
- 1.2 为什么学习 MyBatis
- 二、MyBatis 在软件开发框架中的定位
- 三、基于 Spring Boot 创建 MyBatis 项目
- 3.1 添加 MyBatis 框架的支持
- 3.2 配置数据库连接信息和映射文件的保存路径(Mapper XML)
- 四、MyBatis 项目结构的创建与使用
- 4.1 数据库和表的准备
- 4.2 根据数据库表创建实体类
- 4.3 创建 Mapper 接口和 XML 映射文件
- 4.4 创建服务层 Service 和 控制层 Controller
- 五、通过 MyBatis 实现增、删、改操作
- 5.1 增加用户
- 5.2 修改用户
- 5.3 删除用户
- 六、通过 MyBatis 实现查询操作
- 6.1 单表查询
- 6.1.1 通过用户 ID 查询
- 6.1.2 参数占位符 #{} 和 ${}
- 6.1.3 SQL 注入问题
- 6.1.3 like 查询
- 6.1.4 使用 resultMap 解决实体类参数与数据库表字段不匹配问题
- 6.2 多表查询
- 6.2.1 VO类的创建
- 6.2.2 创建 Mapper 接口和 XML 映射文件
- 6.2.3 查询文章详情
- 七、MyBatis 动态 SQL 的使用
- 7.1 if 标签
- 7.2 trim 标签
- 7.3 where 标签
- 7.4 set 标签
- 7.5 foreach 标签
前言
在软件开发领域,持久层框架的选择对于项目的实现和维护起着至关重要的作用。MyBatis 作为一款优秀的持久层框架,以其灵活性、高度可定制化以及对SQL的直接控制等特性而广受关注和应用。本文将深入探索 MyBatis 框架,从初识到实际应用,逐步揭示其在现代软件开发中的关键作用。
一、初识 MyBatis
1.1 什么是 MyBatis
MyBatis 是一个优秀开源的 Java 持久层框架,用于简化数据库访问和操作的过程。它允许开发者使用简单的XML或注解配置来映射 Java 对象与数据库表之间的关系,从而实现数据库的持久化操作。MyBatis并不是一个全面的ORM(对象关系映射)框架,而是更强调对 SQL 的精确控制,使开发者能够更直接地编写和优化 SQL 语句。
ORM(对象关系映射)框架:
ORM,全称为对象关系映射(Object-Relational Mapping),是一种软件技术,用于将面向对象的编程语言(如Java、Python等)中的对象模型与关系型数据库中的数据模型之间进行映射和转换。简单来说,ORM框架允许开发者使用面向对象的思维来操作数据库,而不需要直接编写SQL语句。
MyBatis 的核心思想在于 SQL 的分解,它将 SQL 语句与 Java 代码分开,从而降低了代码的耦合度,提供了更大的灵活性和可维护性。通过配置映射文件(Mapper XML),开发者可以将SQL语句和查询结果的映射关系定义清晰,而Java代码则专注于业务逻辑的编写。此外,MyBatis还支持动态SQL、参数绑定、缓存等特性,使得数据库操作更加高效和便捷。
1.2 为什么学习 MyBatis
对于后端开发来说,程序是由以下两个重要的部分组成的,即 后端程序 和 数据库。

这两个重要的组成部分要通讯,就要依靠数据库连接工具,比如之前的 JDBC 以及现在的 MyBatis 框架,都是为了连接并操作数据库。
然而使用 JDBC 的操作会非常的繁琐,因此就需要使用其他更加简单高效的数据库连接方式了,而 MyBatis 就是一个更好的选择。
MyBatis 作为一个持久层框架,在现代软件开发中具有许多优势和价值,学习 MyBatis 的主要原因有:
1. 灵活的SQL 控制: MyBatis 允许开发者直接编写和控制 SQL 语句,这对于需要对数据库操作进行精确控制和优化的场景非常有用。开发人员可以编写自己的SQL语句,根据具体需求进行调整,而不受自动生成的SQL的限制。
2. 良好的性能: 由于开发者可以优化 SQL 语句,使其更适合特定的数据库和查询需求,因此 MyBatis 在性能方面表现出色。合理编写和优化的 SQL 语句可以显著提升应用程序的数据库访问效率。
3. 适应不同数据库: MyBatis 支持多种数据库,因此无论使用哪种关系型数据库(如MySQL、Oracle、SQL Server等),MyBatis 都可以适应并提供一致的操作方式。
4. 良好的扩展性: MyBatis 允许开发者编写自定义的 TypeHandlers、Plugins 等来满足特定需求,从而增强了框架的扩展性和定制性。
5. 轻量级框架: 相对于一些重量级的 ORM 框架,MyBatis 是一个相对轻量级的框架,学习成本较低,上手相对容易。
6. 可与其他框架集成: MyBatis 可以很容易地与其他流行的框架(如Spring、Spring Boot)进行集成,使得整体开发流程更加顺畅。
7. 更好地理解数据库: 通过学习 MyBatis,将不仅仅是在学习一个框架,还会更深入地理解数据库的工作方式和性能优化方法,这对于数据库设计和应用优化都有很大帮助。
二、MyBatis 在软件开发框架中的定位
理解 MyBatis 在整个软件开发框架中的定位是非常重要的,特别是对于了解其在系统架构中的作用和角色有帮助。下面是一个简单的交互流程图,展示了 MyBatis 在整个应用架构中的位置和交互关系:

在上述流程中,MyBatis主要位于持久层(Persistence),它的作用是将业务逻辑和数据库之间的交互进行封装和管理。下面是各层之间的交互关系:
-
前端界面:这是应用程序的用户界面,用户通过界面与系统进行交互,发送请求。
-
控制层 (Controller):控制层接收来自用户界面的请求,处理请求的分发和调度,调用适当的服务层进行业务处理。
-
服务层 (Service):服务层包含了应用程序的业务逻辑。它接收控制层传递的请求,处理业务逻辑,并可能需要与持久层进行数据交互。
-
持久层 (Persistence - MyBatis):MyBatis 位于持久层,它负责将业务逻辑中的数据访问需求转化为对数据库的操作。通过映射文件(Mapper XML)和对应接口(Mapper Interface)进行关系映射,MyBatis 将 Java 对象和数据库表之间的数据转换进行管理。
-
数据库 (DB):数据库是存储实际数据的地方。MyBatis通过SQL语句执行实际的数据库操作,将数据存储、检索、更新等操作反映到数据库中。
在这个流程中,MyBatis 在持久层起到了桥梁的作用,负责将业务逻辑与数据库操作连接起来。它允许开发者通过映射文件或注解定义数据库表与 Java 对象之间的关系,从而实现数据的存取。这种定位使得开发者能够充分利用数据库的性能和功能,同时保持代码的可维护性和可扩展性。
三、基于 Spring Boot 创建 MyBatis 项目
3.1 添加 MyBatis 框架的支持
- 创建 Spring Boot 项目

- 添加 MyBatis 依赖

在创建 Spring Boot 项目的时候,如果想要创建 MyBatis 项目,需要在依赖中勾选MyBatis Framework,除此之外,还需要勾选一个具体的数据库驱动,比如MySQL Driver。
3.2 配置数据库连接信息和映射文件的保存路径(Mapper XML)
在创建好 Spring Boot 项目后,还需要在 application.yml 配置文件中为 MyBatis 配置数据库连接信息和映射文件的保存路径(Mapper XML)。
配置的内容如下:
# 配置数据库的连接字符串
spring:datasource:url: jdbc:mysql://127.0.0.1:3306/database?characterEncoding=utf8username: rootpassword: passworddriver-class-name: com.mysql.cj.jdbc.Driver
这部分配置用于设置数据库连接信息。需要根据实际情况修改url、username 和 password 字段,以连接到自己的 MySQL 数据库。其中driver-class-name 字段指定了MySQL数据库驱动程序的类名。
# 设置 Mybatis 的 xml 保存路径,在 resources/mapper 创建所有表的 xml ⽂件
mybatis:mapper-locations: classpath:mapper/*Mapper.xml
这部分配置设置了 Mapper XML 文件的保存路径。mapper-locations 字段指定了 MyBatis 应该在classpath:mapper/ 路径下查找 Mapper XML文件。首先需要在这个路径下创建与MyMapper接口对应的Mapper XML文件,才能够使用 MyBatis。
四、MyBatis 项目结构的创建与使用
4.1 数据库和表的准备
此处创建一个userinfo表和 articleinfo表:
-- 创建数据库
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 timestamp default current_timestamp,updatetime timestamp default current_timestamp,`state` int default 1
) default charset 'utf8mb4';-- 创建文章表
drop table if exists articleinfo;
create table articleinfo(id int primary key auto_increment,title varchar(100) not null,content text not null,createtime timestamp default current_timestamp,updatetime timestamp default current_timestamp,uid int not null,rcount int not null default 1,`state` int default 1
)default charset 'utf8mb4';-- 添加一个用户信息
INSERT INTO `mycnblog`.`userinfo` (`id`, `username`, `password`, `photo`, `createtime`, `updatetime`, `state`) VALUES
(1, 'admin', 'admin', '', '2023-8-09 10:10:48', '2023-8-09 10:10:48', 1);-- 文章添加测试数据
insert into articleinfo(title,content,uid) values('Java','Java正文',1);
insert into articleinfo(title,content,uid) values('C++','C++正文', 1);
insert into articleinfo(title,content,uid) values('Python','Python', 1);
insert into articleinfo(title,content,uid) values('PHP','PHP正文', 1);
4.2 根据数据库表创建实体类
例如针对表 userinfo 创建一个实体类:
@Data
public class UserInfo {private Integer id;private String username;private String password;private String photo;private LocalDateTime createtime;private LocalDateTime updatetime;private Integer state;
}
实体类中的属性名称,为了更好的兼容性,一般与数据库表中的字段相匹配。此处使用了 Lombok 库中的@Data 注解来自动生成实体类getter、setter、equals、hashCode 和 toString等方法,这样可以减少样板代码的编写。
4.3 创建 Mapper 接口和 XML 映射文件
上述实体类已经包含了与数据库表字段对应的属性,以及对应的数据类型。只需要确保在使用MyBatis时,Mapper 接口和 Mapper XML 文件与该实体类正确匹配。可以创建一个对应的Mapper接口和XML文件,然后使用@Mapper注解标记接口。
在 mapper 目录下创建 UserMapper 接口:

其中,@Mapper 注解是 MyBatis 中的一个注解,用于标记一个接口为 MyBatis 的 Mapper 接口,从而告诉 MyBatis 这个接口定义了数据库操作的方法。在这个接口中,只需编写与数据库操作的相关代码即可,比如,获取所有的 User 信息:
import com.example.demo.entity.UserInfo;import java.util.List;@Mapper
public interface UserInfoMapper {List<UserInfo> getAll();
}
创建 XML 映射文件:
-
首先在
resources目录下创建一个mapper子目录,用于存放Mapper XML文件:

-
在这个
mapper路径下创建与UserInfoMapper.java接口对应的 XML 文件UserInfoMapper.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.demo.mapper.UserInfoMapper"></mapper>
其中 namespace 字段指定的就是与 UserInfoMapper.xml 对应的 UserInfoMapper 接口的路径,此时便建立了 XML 文件与接口之间的映射关系。
当在 IDEA 中安装了 MyBatisX 插件,就可以发现出现了一对小鸟,此时点击 UserInfoMapper.xml边上的蓝色小鸟,就可以调整到与之映射的 UserInfoMapper 接口中。

反之,点击UserInfoMapper 接口中的红色小鸟,也会跳转到与之对应的UserInfoMapper.xml文件中。

此时,发现接口中的方法会报错,那是因为在 Mapper XML文件中没有与之对应的 SQL 语句。
实现 getAll 方法对应的 SQL 语句:
在UserInfoMapper.xml编写查询所有用户的SQL语句:

其中,id字段指定的是与这个 SQL 语句对应的 Mapper接口中方法,即getAll,而resultType 字段则是返回数据的类型,此处返回的是UserInfo对象,MyBatis 框架会根据这个映射配置,在查询执行完成后,将查询结果自动映射到 UserInfo 对象中。但是前提条件是,要确保实体类中的属性名称与数据库表的字段名称相匹配,这样 MyBatis 才能正确地进行结果映射。
4.4 创建服务层 Service 和 控制层 Controller
- 创建服务层
service目录,然后在该目录下创建UserInfoService类:
@Service
public class UserInfoService {@Autowiredprivate UserInfoMapper userInfoMapper;public List<UserInfo> getAll(){return userInfoMapper.getAll();}
}
- 创建控制层
controller目录,然后在该目录下创建UserInfoController类:
@RequestMapping("/user")
@RestController
public class UserInfoController {@Autowiredprivate UserInfoService userInfoService;@GetMapping("/getAll")public List<UserInfo> getAll(){return userInfoService.getAll();}
}
此时已经完成了服务层和控制层的创建,其中控制层负责处理 HTTP 请求,以及与用户、服务层之间的交互;而服务层用于处理与用户信息相关的业务逻辑,并向控制层返回处理的结果。这种结构符合典型的三层架构(Controller - Service - Repository/DAO)设计模式,让代码更加清晰和易于维护。
在上述的代码中,UserInfoService 负责调用 UserInfoMapper 执行数据库操作,而 UserInfoController 则负责处理 HTTP 请求,将业务逻辑和数据库操作分离。
在这个基本的结构,允许通过访问/user/getAll来获取所有用户信息,例如此时运行服务器,然后在浏览器中输入http://localhost:8080/user/getAll进行访问,可以看到获取到了数据库中的所有用户信息:

五、通过 MyBatis 实现增、删、改操作
5.1 增加用户
1. 在UserInfoMapper 接口中添加一个 addUser 方法:
// 增加用户
int addUser(UserInfo user);
2. 在UserInfoMapper.xml中编写对应的 SQL 语句:
<insert id="addUser">insert into userinfo(username, password) values (#{username}, #{password})
</insert>
3. 此时,可以对 addUser 进行单元测试
1)首先在UserInfoMapper接口中点击右键,然后选择Generate:

2)选择其中的Test:

3)创建单元测试类

此处选择添加测试addUser方法。添加完成后,可以在 test 目录下找到对应的测试类:

4)编写测试代码

简单说明:
- 在测试代码中,使用了
@SpringBootTest注解,表示这是一个 Spring Boot 测试。 @Autowired注解用于自动注入UserInfoMapper,允许在测试中使用它。@Transactional注解用于表示测试过程中的事务操作,它会在测试结束时回滚,以避免对数据库造成实际的影响。
运行该测试代码,发现通过测试,则说明刚才的代码是正确的:

5.2 修改用户
例如,此时需要通过用户 id 来修改用户名:
1. 在UserInfoMapper 接口中添加一个 updateUserById 方法:
// 根据id修改用户名
int updateUserById(Integer id, String username);
2. 在UserInfoMapper.xml中编写对应的 SQL 语句:
<update id="updateUserById">update userinfo set username=#{username} where id=#{id}
</update>
3. 进行单元测试:
1)添加测试方法
@Test
void updateUserById() {
}
2)编写测试代码
此时 userinfo 表中的内容有:

要求把 id 为 1 的用户名修改为 admin:
@Test
void updateUserById() {Integer id = 1;String username = "admin";int res = userInfoMapper.updateUserById(id, username);System.out.println("影响行数:" + res);
}
3)运行该测试方法
执行成功:

再次查看 userinfo 表,发现已经成功进行了修改:

5.3 删除用户
现在,要求通过用户id删除指定用户:
1. 在UserInfoMapper 接口中添加一个 deleteUserById 方法:
// 根据 id 删除用户
int deleteUserById(Integer id);
2. 在UserInfoMapper.xml中编写对应的 SQL 语句:
<delete id="deleteUserById">delete from userinfo where id=#{id}
</delete>
3. 进行单元测试:
1)添加测试方法
@Test
void deleteUserById() {
}
2)编写测试方法
此时要删除 id 为 12 的用户:
@Testvoid deleteUserById() {Integer id = 12;int res = userInfoMapper.deleteUserById(id);System.out.println("影响行数:" + res);}
3)运行测试代码
测试通过:

发现此时userinfo表中 id 为 12 的用户被删除了:

六、通过 MyBatis 实现查询操作
6.1 单表查询
6.1.1 通过用户 ID 查询
1. 在UserInfoMapper接口中添加getUserById方法:
// 根据id查询用户
UserInfo getUserById(Integer id);
2. 在UserInfoMapper.xml中编写对应的 SQL:
使用 #{} 参数占位符:
<select id="getUserById" resultType="com.example.demo.entity.UserInfo">select * from userinfo where id=#{id}
</select>
使用 ${} 参数占位符:
<select id="getUserById" resultType="com.example.demo.entity.UserInfo">select * from userinfo where id=${id}
</select>
3)进行单元测试
查询id 为 1 的用户:
@Test
void getUserById() {UserInfo user = userInfoMapper.getUserById(1);System.out.println(user);
}
使用 #{} 参数占位符的运行结果:

使用 ${} 参数占位符的运行结果:

通过上述的测试代码不难发现:
- 在使用
#{}参数占位符的时候,准备执行的SQL语句中的参数位置为?,即经过了 SQL 的预编译,后面还需要对这个?进行赋值操作; - 而使用
${}参数占位符的时候参数是直接替换的。
6.1.2 参数占位符 #{} 和 ${}
在 MyBatis 中,#{} 和 ${} 是两种常用的参数占位符,用于在 SQL 语句中引用参数值。虽然它们看起来类似,但在使用时有一些重要的区别。
1. #{} 占位符:
#{}占位符在 SQL 语句中使用时,会自动进行预编译,防止 SQL 注入攻击,并且能够处理参数的类型转换。它适用于大多数的 SQL 参数,如字符串、数字等。
2. ${} 占位符:
${}占位符在 SQL 语句中使用时,会将参数值直接嵌入到 SQL 语句中,不进行预编译。这可能会导致 SQL 注入风险,因此需要谨慎使用。它适用于一些特殊的场景,如动态表名或列名等。
因此,在大多数情况下推荐尽可能使用 #{} 占位符,以确保 SQL 的安全性和可维护性。只在必要的情况下使用 ${} 占位符,同时保证输入参数的合法性和安全性。
6.1.3 SQL 注入问题
下面通过使用
${}模拟登录时发生的 SQL 注入问题:
1. 在UserInfoMapper接口中添加getUserById方法:
// 实现登录操作
UserInfo login(UserInfo user);
2. 在UserInfoMapper.xml中编写对应的 SQL:
<select id="login" resultType="com.example.demo.entity.UserInfo">select * from userinfo where username='${username}' and password='${password}'
</select>
由于使用${}是直接进行参数替换的,因此需要在${}外面加上''。
3. 编写单元测试
首先进行正常的演示:
@Test
void login(){String username = "zhangsan";String password = "123456";UserInfo user = new UserInfo();user.setUsername(username);user.setPassword(password);UserInfo loginUser = userInfoMapper.login(user);System.out.println(loginUser);
}
此时可以成功获取到对象:

但是如果将 password 改成:
String password = "' or 1='1";
再次运行测试代码:

发现此时获取到了数据库中的全部内容,其执行的 SQL 语句是:
select * from userinfo where username='zhangsan' and password='' or 1='1'
即不管输入的username 和 password 是否正确,where 条件始终为 true,这就是 SQL 注入带来的风险。
如果此时将 ${} 改为 #{}:
<select id="login" resultType="com.example.demo.entity.UserInfo">select * from userinfo where username=#{username} and password=#{password}
</select>
再次运行刚才的代码:

此时通过预编译然后再获取参数,避免了 SQL 注入带来的风险。
6.1.3 like 查询
使用 like 通过用户名模糊查询:
1. 在UserInfoMapper接口中添加getListByName方法:
// like 模糊查询
List<UserInfo> getListByName(@Param("username") String username);
2. 在UserInfoMapper.xml中编写对应的 SQL:
使用#{}参数占位符:
<select id="getListByName" resultType="com.example.demo.entity.UserInfo">select * from userinfo where username like '%#{username}%'
</select>
此时通过单元测试,发现最后会报错:

这是因为当使用#{}时,最终形成的 SQL 语句是:
select * from userinfo where username like '%'ang'%'
而这是一条错误的 SQL 语句,所有会报错,因此使用 like 查询的时候需要使用${}参数占位符进行直接替换。
<select id="getListByName" resultType="com.example.demo.entity.UserInfo">select * from userinfo where username like '%${username}%'
</select>
再次运行测试代码,发现可以成功查找了:

但是这样还是存在 SQL 注入问题,所以还是需要使用 #{},对于这种情况,可以使用 MySQL 的内置函数 concat 来解决:
<select id="getListByName" resultMap="BaseMap">select *from userinfowhere username like concat('%', #{username}, '%');
</select>
其中,concat的作用就是拼接字符串,并且支持可变参数。
6.1.4 使用 resultMap 解决实体类参数与数据库表字段不匹配问题
有时候,我们程序中实体类中的参数名可能会和数据库表中的字段名不匹配,那么 MyBatis 就无法正确绑定查询结果到实体类对象了,此时可以使用 Mapper XML 中的 resultMap 来解决。
例如,userinfo表中的密码字段为 password,而实体类中的属性名为 pwd,此时再通过getUserById来查询用户,最后发现pwd属性为空:

此时,在UserInfoMapper.xml文件中新加入一个 resultMap 标签:

简单说明:
-
<id>元素:定义了主键的映射。column属性指定数据库表的列名,property属性指定实体类的属性名。在这个示例中,数据库表的主键列 “id” 映射到实体类的属性 “id”。 -
<result>元素:定义了普通列的映射。column属性指定数据库表的列名,property属性指定实体类的属性名。在这个示例中,数据库表的 “username” 列映射到实体类的属性 “username”,“password” 列映射到实体类的属性 “pwd”,“photo” 列映射到实体类的属性 “photo”。
然后修改 getUserById 方法对应的 SQL,修改其返回结果为字典映射 baseMap:
<select id="getUserById" resultMap="baseMap">select * from userinfo where id=${id}
</select>
再次运行测试代码,就可以拿到正确的结果了:
当然,也可以在 SQL 语句中,将 password 重命名为 pwd 来解决这个问题,例如:
<select id="getUserById" resultType="com.example.demo.entity.UserInfo">select id, username, password as pwd, photo, createtime, updatetime, state from userinfo where id=${id}
</select>
此时同样可以拿到正确的结果:

6.2 多表查询
6.2.1 VO类的创建
在进行代表查询的时候,通常都需要创建一个值对象(VO,Value Object)来包含多个表的相关信息。VO类是一个 Java 类,通常用于封装多个实体类的属性,从而方便在多个表查询中传递和处理数据。
例如,此时需要通过文章 id 来查询文章详情,而文章详情中需要包含用户名,但articleInfo 表中只有用户 uid,所有就需要进行多表查询。为了方便将用户名和文章信息相结合,因此就需要额外创建一个ArticleInfoVO类。
首先创建 articleinfo 表对应的实体类 ArticleInfo:

然后继承该类,在vo目录下创建一个 ArticleInfoVO 类:

6.2.2 创建 Mapper 接口和 XML 映射文件
1. 创建 Mapper 接口 ArticleInfoVOMapper:

2. 创建 XML 映射文件 ArticleInfoVOMapper.xml:

6.2.3 查询文章详情
1. 在 ArticleInfoVOMapper接口中创建方法getDetial:
// 通过文章 id 查询文章详情
ArticleInfoVO getDetial(Integer id);
2. 在 ArticleInfoVOMapper.xml文件中编写对应的 SQL 语句:
<select id="getDetial" resultType="com.example.demo.entity.vo.ArticleInfoVO">select a.*, u.username from articleinfo a left join userinfo u on a.uid = u.id where a.id = #{id}
</select>
3. 编写单元测试
@SpringBootTest
class ArticleInfoVOMapperTest {@Autowiredprivate ArticleInfoVOMapper articleInfoVOMapper;@Testvoid getDetail() {ArticleInfoVO detail = articleInfoVOMapper.getDetail(1);System.out.println(detail);}
}
运行测试代码,发现能正确查找出结果:

七、MyBatis 动态 SQL 的使用
MyBatis 动态 SQL 是指根据不同的条件和参数,动态地生成 SQL 查询或更新语句的过程。它允许在编写 SQL 映射文件时,根据业务需求来动态组装 SQL 语句的各个部分,从而实现更灵活的数据库操作。动态 SQL 在处理不同的查询条件、排序、过滤等方面非常有用,它可以避免因为多种情况而编写大量重复的 SQL 语句,从而提高开发效率。
MyBatis 提供了一系列的 XML 标签和语法,用于构建动态 SQL。这些标签可以用来包含条件判断、循环遍历、动态拼接 SQL 片段等操作。一些常用的动态 SQL 标签包括 <if>、<choose>、<when>、<otherwise>、<trim>、<where>、<set>、<foreach> 等,详情可以参考MyBatis 官网: 动态SQL。
总之,MyBatis 动态 SQL 是一种强大的机制,使得在 SQL 映射文件中根据不同情况生成合适的 SQL 语句变得更加灵活和方便。下面是对一些常见的动态 SQL 标签的详细介绍。
7.1 if 标签
<if> 标签用于在 SQL 语句中添加条件判断,根据条件的真假来动态生成 SQL 片段。
例如,在添加用户信息的时候,photo 字段的内容可能不确定用户是否输入,这时就需要使用 <if> 标签来构建动态 SQL:
<insert id="addUser">insert into userinfo(username,<if test="photo!=null and photo!=''">photo,</if>password)values (#{username},<if test="photo!= null and photo!=''">#{photo},</if>#{pwd})
</insert>
需要注意的是,其中 <if>标签中的 test 属性指定的是传入的对象的属性,而不是数据库表中的字段。
在单元测试中,只输入 username 和 password,最后形成的 SQL 也只有这两个字段:

如果在增加输入一个photo 属性:

可以发现此时三个字段都有。
7.2 trim 标签
如果当输入的所有属性都是可选的情况下,那么只使用 <if> 标签就不能解决其中的 , 问题了,因为不知道,在哪个位置出现,可以出现在前面,也可能在后面,如果没有输入的话可能都不出现。因此,要解决这个问题就需要引入<trim>标签。
<trim>标签属性:
prefix:表示整个语句块,以prefix的值作为前缀suffix:表示整个语句块,以suffix的值作为后缀prefixOverrides:表示整个语句块要去除掉的前缀suffixOverrides:表示整个语句块要去除掉的后缀
例如,此时设置添加用户时的 username、password、photo 三个字段都是可选的:
<insert id="addUser">insert into userinfo<trim prefix="(" suffix=")" suffixOverrides=","><if test="username!=null and username!=''">username,</if><if test="photo!=null and photo!=''">photo,</if><if test="pwd!=null and pwd!=''">password,</if></trim>values<trim prefix="(" suffix=")" suffixOverrides=","><if test="username!=null and username!=''">#{username},</if><if test="photo!=null and photo!=''">#{photo},</if><if test="pwd!=null and pwd!=''">#{pwd},</if></trim>
</insert>
其中,<trim> 标签的作用就是用于修剪插入的列名和值部分,可以在开始和结束位置删除多余的逗号。prefix 属性表示在 SQL 片段前添加的内容,suffix 属性表示在 SQL 片段后添加的内容,suffixOverrides 属性表示在 SQL 片段结尾删除的内容。
7.3 where 标签
<where> 标签用于将条件添加到 SQL 语句的 WHERE 子句中,并处理条件之间的逻辑。
例如,现在可以通过文章的 id 或者 title 来进行查询,其中 id 和 title 的内容都是可选输入项,并且title使用的是模糊匹配。
<select id="getListByIdOrTitle" resultType="com.example.demo.entity.vo.ArticleInfoVO">select a.*, u.username from articleinfo aleft join userinfo u on a.uid = u.id<where><if test="id != null and id > 0">and a.id = #{id}</if><if test="title!=null and title!=null">and a.title like concat('%', #{title}, '%')</if></where>
</select>
另外,<where>标签会自动去除前缀的and。
当然,也可以使用 <trim> 和 <if> 标签来实现这个功能:
<select id="getListByIdOrTitle" resultType="com.example.demo.entity.vo.ArticleInfoVO">select a.*, u.username from articleinfo aleft join userinfo u on a.uid = u.id<trim prefix="where" prefixOverrides="and"><if test="id != null and id > 0">and a.id = #{id}</if><if test="title!=null and title!=null">and a.title like concat('%', #{title}, '%')</if></trim>
</select>
此时需要使用<trim>标签,用来去除一个前缀and,以及添加一个前缀where。
7.4 set 标签
<set> 标签用于在更新语句中设置需要更新的字段,并根据条件动态生成更新语句。
例如,通过用户 id 来修改该用户不为 null 的属性:
<update id="updateById">update userinfo<set><if test="username!=null and username!=''">username=#{username},</if><if test="pwd!=null and pwd!=''">password=#{pwd},</if><if test="photo!=null and photo!=''">photo=#{photo},</if></set>where id=#{id}
</update>
<set>标签和<where> 相反,它只会去除后缀的, 。
7.5 foreach 标签
<foreach> 标签用于遍历集合或数组,并将其中的元素添加到 SQL 语句中。
<foreach>标签有如下属性:
collection:绑定⽅法参数中的集合,如 List,Set,Map或数组对象item:遍历时的每⼀个对象open:语句块开头的字符串close:语句块结束的字符串separator:每次遍历之间间隔的字符串
例如,现在需要根据多个文章 id来删除对应的文章:
1. 在ArticleInfoVOMapper接口中添加方法:
// 根据多个文章 `id`来删除对应的文章
int deleteByIds(List<Integer> ids);
2. 在ArticleInfoVOMapper.xml编写对应SQL:
<delete id="deleteByIds">delete from articleinfo where id in<foreach collection="ids" item="item" open="(" close=")" separator=",">#{item}</foreach>
</delete>
这段代码演示了使用 MyBatis 的动态 SQL 构建删除语句的示例。这个删除语句会根据给定的 ID 列表,动态地生成 DELETE 语句中的 IN 子句,从而批量删除满足条件的记录。
简单说明:
-
<delete>标签:这个标签表示一个删除语句的定义。 -
<foreach>标签:这个标签用于遍历集合,将集合中的元素添加到 SQL 语句中。在这个示例中,它会将ids集合中的每个元素添加到IN子句中,形成类似(id1, id2, id3)的结构。collection属性:指定要遍历的集合。item属性:指定在遍历过程中每个元素的别名。open属性:指定遍历开始时的字符,这里是(。close属性:指定遍历结束时的字符,这里是)。separator属性:指定元素之间的分隔符,这里是逗号,。
通过这种方式,可以使用动态 SQL 构建批量删除语句,根据给定的 ID 集合删除相应的记录。
3. 进行单元测试:
@Transactional
@Test
void deleteByIds() {List<Integer> ids = new ArrayList<>();ids.add(1);ids.add(2);ids.add(3);ids.add(4);int res = articleInfoVOMapper.deleteByIds(ids);System.out.println("影响行数:" + res);
}
测试通过:

相关文章:
MyBatis 实战指南:探索灵活持久化的艺术
文章目录 前言一、初识 MyBatis1.1 什么是 MyBatis1.2 为什么学习 MyBatis 二、MyBatis 在软件开发框架中的定位三、基于 Spring Boot 创建 MyBatis 项目3.1 添加 MyBatis 框架的支持3.2 配置数据库连接信息和映射文件的保存路径(Mapper XML) 四、MyBati…...
高中教师能去美国做访问学者吗?
美国作为世界上高等教育水平较高的国家之一,吸引了众多学者前往交流学习。那么高中教师是否能够成为美国访问学者,这是当然的,高中老师是可以出国访学的,但是出国做访问学者会涉及到多方面的因素。 首先,教师个人的学术…...
93 | Python 设计模式 —— 建造者模式
文章目录 什么是建造者模式?建造者模式的四个角色Python建造者模式示例建造者模式的优点建造者模式的适用场景案例1:股票价格监控案例2:天气预报系统总结当构建复杂对象时,经常会遇到对象的创建过程非常复杂、包含多个步骤、或者有不同的配置选项。这时,使用建造者模式是一…...
nacos升级开启鉴权后,微服务无法连接的解决方案
版本: 软件版本号备注spring boot2.2.5.RELEASEspring-cloudHoxton.SR3spring-cloud-alibaba2.2.1.RELEASEnacos2.0.1从1.4.2版本进行升级。同时作为注册中心和配置中心 一、升级nacos版本,开启鉴权 1.在application.properties配置文件开启鉴权&…...
elementui弹窗页按钮重复提交问题解决
一、BUG场景 ruoyi平台,页面弹出窗有提交按钮,在提交时连续多次点击会发生重复提交。 二、错误方案 给按钮增加 :loading"submitLoading" 属性。 <el-dialog :title"title" :v-if"open" :visible.sync"open&…...
HBase-读流程
创建连接同写流程。 (1)读取本地缓存中的Meta表信息;(第一次启动客户端为空) (2)向ZK发起读取Meta表所在位置的请求; (3)ZK正常返回Meta表所在位置&#x…...
Matlab绘图 图例legend 太长,怎么减小指示线的长度
来源 绘图时,稍微减小文字已经不能正常放下图例,想通过调整图例指示线段长度缩减整个图例长度。 方法一 参考matlab官方论坛 leg legend(Plot1,Plot2,...); leg.ItemTokenSize [x1,x2]; By default x130 and x218 so put larger or smaller number…...
力扣17(电话号码中的字符组合)
题目表述 给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。 给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。 示例1 输入:digits "23" 输出࿱…...
vue+element 下载压缩包和导出
export function goodsInspectionReportDwnloadZip (params) {return axios({url: "/warehouse-entry-server/v1/goodsInspectionReport/downloadZip",method: "get",params,responseType: "blob"}) } //下载handleDownloadFile() {if (!this.$r…...
构建Docker容器监控系统 (1)(Cadvisor +InfluxDB+Grafana)
目录 Cadvisor InfluxDBGrafana 1. Cadvisor 2.InfluxDB 3.Grafana 开始部署: 下载组件镜像 创建自定义网络 创建influxdb容器 创建数据库和数据库用户 创建Cadvisor 容器 准备测试镜像 创建granafa容器 访问granfana 添加数据源 Add data source 新建 …...
hive编译报错整理
背景 最近在修hive-1.2.0的一个bug,需要修改后重新打包部署到集群,打包的时候报下面的错误,原因很简单,从远程仓库里面已经拉不到这个包了。 org.pentaho:pentaho-aggdesigner-algorithm:jar:5.1.5-jhyde was not found in http…...
centos磁盘爆满可以清理mysql-bin.000011吗
mysql-bin.000011 是 MySQL 的二进制日志文件,用于记录数据库中的更改操作。删除该文件可能会导致数据库恢复、备份和复制等功能的中断或数据丢失。因此,在删除任何 MySQL 的二进制日志文件之前,请确保您了解其潜在影响并采取适当的备份措施。…...
SSM个人博客项目
文章目录 SSM个人博客系统实现项目介绍 一、准备工作0. 创建项目添加对应依赖1. 数据库设计2. 定时实体类 二、功能实现1.统一功能处理统一返回格式统一异常处理定义登录拦截器 2. 注册登录实现生成获取验证码密码加盐实现注册功能登录功能注销功能 3.登录用户博客列表获取登录…...
vue插槽是什么?如何使用?
1、意义 插槽是vue提供的一个内置组件,是一个占位符。作用是可以向组件中传递一段html代码,加强了组件封装性以及复用性。 2、分类 插槽通常分为匿名插槽、具名插槽、作用域插槽 匿名插槽: 顾名思义就是没有名字的插槽,我们通…...
yum常用操作命令
目录 查询命令 查看当前所有仓库 检查可升级的程序 安装、卸载、升级 清除缓存命令 生成缓存 查询命令 列出已安装的软件包:yum list installed列出仓库中还未安装的软件包:yum list available列出指定软件包的依赖关系:yum deplist &…...
.Net C# 免费PDF合成软件
最近用到pdf合成,发现各种软件均收费啊,这个技术非常简单,别人写好的库一大把,这里用到了PDFsharp,项目地址Home of PDFsharp and MigraDoc Foundation 软件下载地址 https://download.csdn.net/download/g313105910…...
JAVA集合框架 一:Collection(LIst,Set)和Iterator(迭代器)
目录 一、Java 集合框架体系 1.Collection接口:用于存储一个一个的数据,也称单列数据集合(single)。 2.Map接口:用于存储具有映射关系“key-value对”的集合(couple) 3.Iterator接口&#…...
python ffmpeg合并ts文件
前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家:点击跳转 当你从网站下载了一集动漫,然后发现是一堆ts文件,虽然可以打开,但是某个都是10秒左右,…...
c++map和set剖析
文章参考文献:cplusplus 博主:拖拉机厂第一代码手 gitee:拖拉机厂第一代码手 c专栏:C 目录 🧙🏼♂set剖析🧚🏼set简介🧚🏼set模板参数列表🧚🏼s…...
kubernetes configmap 的data中的文件内容格式错乱
截取一段错乱的配置: kubectl -n monitoring get cm blackbox-exporter-configuration -o yaml apiVersion: v1 data:config.yml: "\"modules\":\n \"http_2xx\":\n \"http\":\n \"preferred_ip_protocol\"…...
Linux应用开发之网络套接字编程(实例篇)
服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …...
conda相比python好处
Conda 作为 Python 的环境和包管理工具,相比原生 Python 生态(如 pip 虚拟环境)有许多独特优势,尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处: 一、一站式环境管理:…...
装饰模式(Decorator Pattern)重构java邮件发奖系统实战
前言 现在我们有个如下的需求,设计一个邮件发奖的小系统, 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其…...
8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂
蛋白质结合剂(如抗体、抑制肽)在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上,高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术,但这类方法普遍面临资源消耗巨大、研发周期冗长…...
Objective-C常用命名规范总结
【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名(Class Name)2.协议名(Protocol Name)3.方法名(Method Name)4.属性名(Property Name)5.局部变量/实例变量(Local / Instance Variables&…...
Nuxt.js 中的路由配置详解
Nuxt.js 通过其内置的路由系统简化了应用的路由配置,使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...
相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...
均衡后的SNRSINR
本文主要摘自参考文献中的前两篇,相关文献中经常会出现MIMO检测后的SINR不过一直没有找到相关数学推到过程,其中文献[1]中给出了相关原理在此仅做记录。 1. 系统模型 复信道模型 n t n_t nt 根发送天线, n r n_r nr 根接收天线的 MIMO 系…...
系统掌握PyTorch:图解张量、Autograd、DataLoader、nn.Module与实战模型
本文较长,建议点赞收藏,以免遗失。更多AI大模型应用开发学习视频及资料,尽在聚客AI学院。 本文通过代码驱动的方式,系统讲解PyTorch核心概念和实战技巧,涵盖张量操作、自动微分、数据加载、模型构建和训练全流程&#…...
【HarmonyOS 5】鸿蒙中Stage模型与FA模型详解
一、前言 在HarmonyOS 5的应用开发模型中,featureAbility是旧版FA模型(Feature Ability)的用法,Stage模型已采用全新的应用架构,推荐使用组件化的上下文获取方式,而非依赖featureAbility。 FA大概是API7之…...
