文章目录
- 前言
- 一、初识 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);
}
测试通过: