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),它的作用是将业务逻辑和数据库之间的交互进行封装和管理。下面是各层之间的交互关系:

  1. 前端界面:这是应用程序的用户界面,用户通过界面与系统进行交互,发送请求。

  2. 控制层 (Controller):控制层接收来自用户界面的请求,处理请求的分发和调度,调用适当的服务层进行业务处理。

  3. 服务层 (Service):服务层包含了应用程序的业务逻辑。它接收控制层传递的请求,处理业务逻辑,并可能需要与持久层进行数据交互。

  4. 持久层 (Persistence - MyBatis):MyBatis 位于持久层,它负责将业务逻辑中的数据访问需求转化为对数据库的操作。通过映射文件(Mapper XML)和对应接口(Mapper Interface)进行关系映射,MyBatis 将 Java 对象和数据库表之间的数据转换进行管理。

  5. 数据库 (DB):数据库是存储实际数据的地方。MyBatis通过SQL语句执行实际的数据库操作,将数据存储、检索、更新等操作反映到数据库中。

在这个流程中,MyBatis 在持久层起到了桥梁的作用,负责将业务逻辑与数据库操作连接起来。它允许开发者通过映射文件或注解定义数据库表与 Java 对象之间的关系,从而实现数据的存取。这种定位使得开发者能够充分利用数据库的性能和功能,同时保持代码的可维护性和可扩展性。

三、基于 Spring Boot 创建 MyBatis 项目

3.1 添加 MyBatis 框架的支持

  1. 创建 Spring Boot 项目

  1. 添加 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

这部分配置用于设置数据库连接信息。需要根据实际情况修改urlusernamepassword 字段,以连接到自己的 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 映射文件:

  1. 首先在resources目录下创建一个mapper子目录,用于存放Mapper XML 文件:

  2. 在这个 mapper 路径下创建与UserInfoMapper.java接口对应的 XML 文件 UserInfoMapper.xml

  1. 然后需要在这个文件中填充以下内容:
<?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

  1. 创建服务层 service目录,然后在该目录下创建 UserInfoService 类:
@Service
public class UserInfoService {@Autowiredprivate UserInfoMapper userInfoMapper;public List<UserInfo> getAll(){return userInfoMapper.getAll();}
}
  1. 创建控制层 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'

即不管输入的usernamepassword 是否正确,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 属性指定的是传入的对象的属性,而不是数据库表中的字段。

在单元测试中,只输入 usernamepassword,最后形成的 SQL 也只有这两个字段:

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


可以发现此时三个字段都有。

7.2 trim 标签

如果当输入的所有属性都是可选的情况下,那么只使用 <if> 标签就不能解决其中的 , 问题了,因为不知道,在哪个位置出现,可以出现在前面,也可能在后面,如果没有输入的话可能都不出现。因此,要解决这个问题就需要引入<trim>标签。

<trim>标签属性:

  • prefix:表示整个语句块,以prefix的值作为前缀
  • suffix:表示整个语句块,以suffix的值作为后缀
  • prefixOverrides:表示整个语句块要去除掉的前缀
  • suffixOverrides:表示整个语句块要去除掉的后缀

例如,此时设置添加用户时的 usernamepasswordphoto 三个字段都是可选的:

<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 来进行查询,其中 idtitle 的内容都是可选输入项,并且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 子句,从而批量删除满足条件的记录。

简单说明:

  1. <delete> 标签:这个标签表示一个删除语句的定义。

  2. <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);
}

测试通过:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/59143.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

一篇文章教会你什么是Linux进程控制

Linux进程控制 进程创建1.fork函数初识1.1那么fork创建子进程时&#xff0c;操作系统都做了什么呢&#xff1f;1.2 父子进程和CPU中的EIP&#xff08;指令指针&#xff09;之间存在一定的关系1.3 fork的常规用法有哪些&#xff1f;1.4 fork调用失败的原因有哪些&#xff1f; 2.…

JDK 8 升级 JDK 17 全流程教学指南

JDK 8 升级 JDK 17 首先已有项目升级是会经历一个较长的调试和自测过程来保证允许和兼容没有问题。先说几个重要的点 遇到问题别放弃仔细阅读报错&#xff0c;精确到每个单词每一行&#xff0c;不是自己项目的代码也要点进去看看源码到底是为啥报错明确你项目引入的包&#x…

生活小妙招之UE custom Decal

因为这几年大部分时间都在搞美术&#xff0c;所以博客相关的可能会鸽的比较多&#xff0c;阿巴阿巴 https://twitter.com/Tuatara_Games/status/1674034744084905986 之前正好看到一个贴花相关的小技巧&#xff0c;正好做一个记录&#xff0c;也在这个的基础上做一些小的拓展…

React中使用mobx管理状态数据使用样例

MobX 是一个身经百战的库&#xff0c;它通过运用透明的函数式响应编程&#xff08;Transparent Functional Reactive Programming&#xff0c;TFRP&#xff09;使状态管理变得简单和可扩展。官网地址&#xff1a;关于 MobX | MobX中文文档 | MobX中文网 安装依赖 mobx-react-…

SMART司马他法则(目标管理)

S代表具体(Specific)&#xff0c;指绩效考核要切中特定的工作指标&#xff0c;不能笼统&#xff1b; M代表可度量(Measurable)&#xff0c;指绩效指标是数量化或者行为化的&#xff0c;验证这些绩效指标的数据或者信息是可以获得的&#xff1b; A代表可实现(Attainable)&…

手搓单链表

文章目录 前言一、链表和顺序表的区别二、什么是单链表单链表分类单链表的结构 三、带头不循环单链表1.单链表的结构体2.带头不循环单链表的初始化和销毁3.带头不循环单链表的头插&#xff0c;尾插和打印4.带头不循环单链表的头删和尾删5.带头不循环单链表的查找&#xff0c;指…

IDEA常用插件介绍

1.CodeGlance&#xff08;CodeGlance Pro&#xff09; 安装后&#xff0c;重新启动编译器即可。 CodeGlance是一款非常好用的代码地图插件&#xff0c;可以在代码编辑区的右侧生成一个竖向可拖动的代码缩略区&#xff0c;可以快速定位代码的同时&#xff0c;并且提供放大镜功能…

源码解析Collections.sort ——从一个逃过单测的 bug 说起

本文从一个小明写的bug 开始&#xff0c;讲bug的发现、排查定位&#xff0c;并由此展开对涉及的算法进行图解分析和源码分析。 事情挺曲折的&#xff0c;因为小明的代码是有单测的&#xff0c;让小明更加笃定自己写的没问题。所以在排查的时候&#xff0c;也经历了前世的500年…

LeetCode_01 精度丢失

1281. 整数的各位积和之差 给你一个整数 n&#xff0c;请你帮忙计算并返回该整数「各位数字之积」与「各位数字之和」的差。 示例 输入&#xff1a;n 234 输出&#xff1a;15 解释&#xff1a; 各位数之积 2 * 3 * 4 24 各位数之和 2 3 4 9 结果 24 - 9 15示例 …

20230807通过ffmpeg将DTS编码的AUDIO音频转换为AAC编码

20230807通过ffmpeg将DTS编码的AUDIO音频转换为AAC编码 2023/8/7 20:04 ffmpeg dts 转AAC 缘起&#xff1a;由于网上找的电影没有中文字幕&#xff0c;有内置的英文字幕&#xff0c;但是还是通过剪映/RP2023识别一份英文字幕备用&#xff01; I:\Downloads\2005[红眼航班]Red E…

Scikit-learn聚类方法代码批注及相关练习

一、代码批注 代码来自&#xff1a;https://scikit-learn.org/stable/auto_examples/cluster/plot_dbscan.html#sphx-glr-auto-examples-cluster-plot-dbscan-py import numpy as np from sklearn.cluster import DBSCAN from sklearn import metrics from sklearn.datasets …

怎么学习JavaScript相关技术? - 易智编译EaseEditing

学习JavaScript相关技术需要一步步地积累知识和实践经验。以下是一些建议的学习步骤和资源&#xff1a; 基础知识&#xff1a; 开始学习JavaScript之前&#xff0c;了解基本的编程概念和术语。你可以通过在线课程、教材或教学视频来学习编程的基础知识。 学习基本语法&#x…