MyBatis 实战指南:探索灵活持久化的艺术

news2024/9/27 23:33:34

文章目录

  • 前言
  • 一、初识 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=utf8
    username: root
    password: password
    driver-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 {
    @Autowired
    private UserInfoMapper userInfoMapper;

    public List<UserInfo> getAll(){
        return userInfoMapper.getAll();
    }
}
  1. 创建控制层 controller 目录,然后在该目录下创建 UserInfoController 类:
@RequestMapping("/user")
@RestController
public class UserInfoController {
    @Autowired
    private 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 的用户:

    @Test
    void 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 userinfo
    where 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 {

    @Autowired
    private ArticleInfoVOMapper articleInfoVOMapper;

    @Test
    void 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 a
    left 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 a
    left 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.coloradmin.cn/o/853593.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

8月11日|CSA研讨会:国标要点解读《信息安全技术 个人信息处理中告知和同意实施指南》

随着网络与数据科技的进步&#xff0c;个人信息在AIGC、元宇宙世界等产业中扮演着愈发关键的角色。如何实施告知并取得个人主体同意是个人信息处理的基本前提&#xff0c;对于企业等处理者而言尤为重要。《个人信息保护法》规定了知情同意的原则和一般规则&#xff0c;但仍有不…

网络适配器和MAC地址

点对点信道&#xff1a;由于目的地只有一个选项&#xff0c;所以数据链路层不需要使用地址。 而在广播信道中&#xff1a; 各个主机如何判断信号是不是发给自己的&#xff1f; 当多个主机连接在同一个广播信道上&#xff0c;要想实现两个主机之间的通信&#xff0c;则每个主机都…

字段附加属性

字段附加属性 除了查看、编辑、必填三个基本属性之外,在Html模板中可以对单个字段添加Sql操作,或者进行字段赋值和日期时间计算,这也是Html模板表单的一大特色,如图D5-4-1所示。 D5-4-1 5.5.2、插入sql操作 插入sql操作是通过写sql查询语句的方式将需要的内容查询出来后…

【人工智能前沿弄潮】—— SAM从提示生成物体mask

SAM从提示生成物体mask Segment Anything Model&#xff08;SAM&#xff09;根据指示所需的对象来预测对象掩码。该模型首先将图像转换为图像嵌入&#xff0c;从而可以从提示中高效地生成高质量的掩码。 SamPredictor类为模型提供了一个简单的接口来提示模型。用户可以首先使…

HTML——格式化文本与段落

&#x1f60a;HTML——格式化文本与段落 &#x1f30f;前言&#x1f3ad;HTML文本标签&#x1f3af;主体内容body标签&#x1f3af;标题字标签&#x1f3af;空格和特殊字符 &#x1f3ad;格式化文本标签&#x1f3af;文本修饰标签&#x1f3af;计算机输出标签&#xff08;成对标…

基于MATLAB小波变换的信号突变点检测

之前在不经意间也有接触过求突变点的问题。在我看来&#xff0c;与其说是求突变点&#xff0c;不如说是我们常常玩的"找不同"。给你两幅图像&#xff0c;让你找出两个图像中不同的地方&#xff0c;我认为这其实也是找突变点在生活中的应用之一吧。回到找突变点位置上…

Linux部署Zabbix主机监控

192.168.136.55 服务端 192.168.136.56 客户端 一、服务端 1.1 安装lamp环境 #关闭防火墙以及SELINUX systemctl disable firewalld systemctl stop firewalld sed -i s/SELINUXenforcing$/SELINUXdisabled/g /etc/selinux/config setenforce 0设置yum源 yum install epe…

Cocos Creator 3.8 后期效果 Shader 编写(2/2) 进阶篇

前言 在上一篇文章中&#xff0c;麒麟子给大家分享了如何在 Cocos Creator 3.8 中的自定义管线中&#xff0c;添加属于自己的后期效果 Shader。 但基于 BlitScreen 的方案&#xff0c;我们只能编写最简单后效 Shader&#xff0c;如果我们想要支持更多复杂的 Shader&#xff0c…

pc端与flutter通信失效, Method not found

报错情况描述&#xff1a;pc端与flutter通信&#xff0c;ios端能实现通信&#xff0c;安卓端通信报错 报错通信代码&#xff1a; //app消息通知window.callbackName function (res) {window?.jsBridge && window.jsBridge?.postMessage(JSON.stringify(res), "…

axios的使用和接口请求统一封装处理

axios官网&#xff1a;axios中文网|axios API 中文文档 | axios 简单封装&#xff1a;配置基础路径和超时时间&#xff0c;还有请求拦截器和响应拦截器 //对axios进行二次封装 import axios from axios//1、利用axios对象的方法create,去创建一个axios实例 const requests …

Redux基础知识,Redux部分源码分析(手写)

复合组件通信的两种方案&#xff1a; 基于props属性实现父子组件通信(或具备相同父亲的兄弟组件)基于context上下文实现祖先和后代组件间的通信(或具备相同祖先的平行组件) 除了以上方案&#xff0c;其实还可以基于公共状态管理&#xff08;Redux&#xff09;实现组件间的通信…

有哪些pdf修改方法?这几种方法学会就够了

有哪些pdf修改方法&#xff1f;PDF是一种非常常见的电子文档格式&#xff0c;它有很多优点&#xff0c;例如可读性强、易于保护、易于打印等等。但是&#xff0c;有时候我们需要对PDF进行修改&#xff0c;例如添加、删除或修改文本、更改图片、合并或分割文件等等。那么今天就给…

对强缓存和协商缓存的理解

浏览器缓存的定义&#xff1a; 浏览器缓存是浏览器在本地磁盘对用户最近请求过的文档进行存储&#xff0c;当访问者再次访问同一页面时&#xff0c;浏览器就可以直接从本地磁盘加载文档。 浏览器缓存分为强缓存和协商缓存。 浏览器是如何使用缓存的&#xff1a; 浏览器缓存…

天津市城市管理委员会莅临道本科技,共同探讨加快推进城市综合执法数字化新模式

2023年8月4日&#xff0c;市城管委处长李春利带队莅临道本科技考察指导&#xff0c;与道本科技董事长王智勇共同探讨加快推进城市综合执法数字化新模式。 会议上&#xff0c;董事长王智勇着重介绍了道本科技最新研发上线的法治大数据应用产品“合规数知法用法平台”。他表示&am…

微信开发之检测僵尸粉的技术实现

简要描述&#xff1a; 检测好友状态 请求URL&#xff1a; http://域名地址/checkZombie 请求方式&#xff1a; POST 请求头Headers&#xff1a; Content-Type&#xff1a;application/jsonAuthorization&#xff1a;login接口返回 参数&#xff1a; 参数名必选类型说明…

《算法和数据结构》算法篇

前言 我大学的时候比较疯狂&#xff0c;除了上课的时候&#xff0c;基本都是在机房刷题&#xff0c;当然&#xff0c;有时候连上课都在想题目&#xff0c;纸上写好代码&#xff0c;一下课就冲进机房把代码敲了&#xff0c;目的很单纯&#xff0c;为了冲排行榜&#xff0c;就像玩…

C++ 派生类成员的标识与访问——作用域分辨符

在派生类中&#xff0c;成员可以按访问属性分为以下四种&#xff1a; &#xff08;1&#xff09;不可访问成员。这是从基类私有成员继承下来的&#xff0c;派生类或是建立派生类对象的模块都无法访问到它们&#xff0c;如果从派生类继续派生新类&#xff0c;也是无法访问的。 &…

OpenLayers入门,OpenLayers视图飞行动画,OpenLayers飞行到指定经纬度位置

专栏目录: OpenLayers入门教程汇总目录 前言 本章实现OpenLayers视图飞行动画,根据经纬度和动画持续时长,飞行到指定地图位置。 上一章中可以直接通过修改中心点和层级跳转到指定位置:《Openlayers入门,Openlayers调整中心点坐标、Openlayers调整缩放级别、Openlayers调…

第八章:Linux信号

系列文章目录 文章目录 系列文章目录前言linux中的信号进程对信号的处理信号的释义 信号的捕捉信号的捕捉signal()信号的捕捉sigaction() 信号的产生通过终端按键产生信号前台进程与后台进程 kill()用户调用kill向操作系统发送信号raise()进程自己给自己发任意信号&#xff08;…

Qt事件过滤器

1 介绍 事件过滤器是一种机制&#xff0c;当某个QObject没有所需要的事件功能时&#xff0c;可将其委托给其它QObject&#xff0c;通过eventFilter成员函数来过滤实现功能。 2 主要构成 委托&#xff1a; ui->QObject1->installEventFilter(QObject2); eventFilter声明 …