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

对于后端开发来说,程序是由以下两个重要的部分组成的,即 后端程序 和 数据库。

MyBatis 实战指南:探索灵活持久化的艺术_第1张图片
这两个重要的组成部分要通讯,就要依靠数据库连接工具,比如之前的 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 实战指南:探索灵活持久化的艺术_第2张图片

在上述流程中,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 项目

MyBatis 实战指南:探索灵活持久化的艺术_第3张图片

  1. 添加 MyBatis 依赖

MyBatis 实战指南:探索灵活持久化的艺术_第4张图片

在创建 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 接口:

MyBatis 实战指南:探索灵活持久化的艺术_第5张图片
其中,@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 文件:
    MyBatis 实战指南:探索灵活持久化的艺术_第6张图片

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

MyBatis 实战指南:探索灵活持久化的艺术_第7张图片

  1. 然后需要在这个文件中填充以下内容:

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 接口中。
MyBatis 实战指南:探索灵活持久化的艺术_第8张图片
反之,点击UserInfoMapper 接口中的红色小鸟,也会跳转到与之对应的UserInfoMapper.xml文件中。
MyBatis 实战指南:探索灵活持久化的艺术_第9张图片

此时,发现接口中的方法会报错,那是因为在 Mapper XML文件中没有与之对应的 SQL 语句。

实现 getAll 方法对应的 SQL 语句:

UserInfoMapper.xml编写查询所有用户的SQL语句:

MyBatis 实战指南:探索灵活持久化的艺术_第10张图片
其中,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 实战指南:探索灵活持久化的艺术_第11张图片

五、通过 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
MyBatis 实战指南:探索灵活持久化的艺术_第12张图片
2)选择其中的Test
MyBatis 实战指南:探索灵活持久化的艺术_第13张图片
3)创建单元测试类

MyBatis 实战指南:探索灵活持久化的艺术_第14张图片
此处选择添加测试addUser方法。添加完成后,可以在 test 目录下找到对应的测试类:

MyBatis 实战指南:探索灵活持久化的艺术_第15张图片
4)编写测试代码

MyBatis 实战指南:探索灵活持久化的艺术_第16张图片
简单说明:

  • 在测试代码中,使用了@SpringBootTest注解,表示这是一个 Spring Boot 测试。
  • @Autowired注解用于自动注入UserInfoMapper,允许在测试中使用它。
  • @Transactional注解用于表示测试过程中的事务操作,它会在测试结束时回滚,以避免对数据库造成实际的影响。

运行该测试代码,发现通过测试,则说明刚才的代码是正确的:
MyBatis 实战指南:探索灵活持久化的艺术_第17张图片

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 表中的内容有:
MyBatis 实战指南:探索灵活持久化的艺术_第18张图片
要求把 id 为 1 的用户名修改为 admin

@Test
void updateUserById() {
    Integer id = 1;
    String username = "admin";
    int res = userInfoMapper.updateUserById(id, username);
    System.out.println("影响行数:" + res);
}

3)运行该测试方法

执行成功:
MyBatis 实战指南:探索灵活持久化的艺术_第19张图片
再次查看 userinfo 表,发现已经成功进行了修改:
MyBatis 实战指南:探索灵活持久化的艺术_第20张图片

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)运行测试代码

测试通过:
MyBatis 实战指南:探索灵活持久化的艺术_第21张图片

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

MyBatis 实战指南:探索灵活持久化的艺术_第22张图片

六、通过 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);
}

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

MyBatis 实战指南:探索灵活持久化的艺术_第23张图片

使用 ${} 参数占位符的运行结果:
MyBatis 实战指南:探索灵活持久化的艺术_第24张图片
通过上述的测试代码不难发现:

  • 在使用 #{} 参数占位符的时候,准备执行的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);
}

此时可以成功获取到对象:
MyBatis 实战指南:探索灵活持久化的艺术_第25张图片

但是如果将 password 改成:

String password = "'  or 1='1";

再次运行测试代码:

MyBatis 实战指南:探索灵活持久化的艺术_第26张图片
发现此时获取到了数据库中的全部内容,其执行的 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>

再次运行刚才的代码:
MyBatis 实战指南:探索灵活持久化的艺术_第27张图片

此时通过预编译然后再获取参数,避免了 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>

此时通过单元测试,发现最后会报错:

MyBatis 实战指南:探索灵活持久化的艺术_第28张图片
这是因为当使用#{}时,最终形成的 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>

再次运行测试代码,发现可以成功查找了:

MyBatis 实战指南:探索灵活持久化的艺术_第29张图片
但是这样还是存在 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属性为空:

MyBatis 实战指南:探索灵活持久化的艺术_第30张图片
此时,在UserInfoMapper.xml文件中新加入一个 resultMap 标签:
MyBatis 实战指南:探索灵活持久化的艺术_第31张图片

简单说明:

  • 元素:定义了主键的映射。column 属性指定数据库表的列名,property 属性指定实体类的属性名。在这个示例中,数据库表的主键列 “id” 映射到实体类的属性 “id”。

  • 元素:定义了普通列的映射。column 属性指定数据库表的列名,property 属性指定实体类的属性名。在这个示例中,数据库表的 “username” 列映射到实体类的属性 “username”,“password” 列映射到实体类的属性 “pwd”,“photo” 列映射到实体类的属性 “photo”。

然后修改 getUserById 方法对应的 SQL,修改其返回结果为字典映射 baseMap

<select id="getUserById" resultMap="baseMap">
    select * from userinfo where id=${id}
select>

再次运行测试代码,就可以拿到正确的结果了:MyBatis 实战指南:探索灵活持久化的艺术_第32张图片

当然,也可以在 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>

此时同样可以拿到正确的结果:
MyBatis 实战指南:探索灵活持久化的艺术_第33张图片

6.2 多表查询

6.2.1 VO类的创建

在进行代表查询的时候,通常都需要创建一个值对象(VO,Value Object)来包含多个表的相关信息。VO类是一个 Java 类,通常用于封装多个实体类的属性,从而方便在多个表查询中传递和处理数据。

例如,此时需要通过文章 id 来查询文章详情,而文章详情中需要包含用户名,但articleInfo 表中只有用户 uid,所有就需要进行多表查询。为了方便将用户名和文章信息相结合,因此就需要额外创建一个ArticleInfoVO类。

首先创建 articleinfo 表对应的实体类 ArticleInfo
MyBatis 实战指南:探索灵活持久化的艺术_第34张图片

然后继承该类,在vo目录下创建一个 ArticleInfoVO 类:
MyBatis 实战指南:探索灵活持久化的艺术_第35张图片

6.2.2 创建 Mapper 接口和 XML 映射文件

1. 创建 Mapper 接口 ArticleInfoVOMapper
MyBatis 实战指南:探索灵活持久化的艺术_第36张图片
2. 创建 XML 映射文件 ArticleInfoVOMapper.xml
MyBatis 实战指南:探索灵活持久化的艺术_第37张图片

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 实战指南:探索灵活持久化的艺术_第38张图片

七、MyBatis 动态 SQL 的使用

MyBatis 动态 SQL 是指根据不同的条件和参数,动态地生成 SQL 查询或更新语句的过程。它允许在编写 SQL 映射文件时,根据业务需求来动态组装 SQL 语句的各个部分,从而实现更灵活的数据库操作。动态 SQL 在处理不同的查询条件、排序、过滤等方面非常有用,它可以避免因为多种情况而编写大量重复的 SQL 语句,从而提高开发效率。

MyBatis 提供了一系列的 XML 标签和语法,用于构建动态 SQL。这些标签可以用来包含条件判断、循环遍历、动态拼接 SQL 片段等操作。一些常用的动态 SQL 标签包括 等,详情可以参考MyBatis 官网: 动态SQL。

总之,MyBatis 动态 SQL 是一种强大的机制,使得在 SQL 映射文件中根据不同情况生成合适的 SQL 语句变得更加灵活和方便。下面是对一些常见的动态 SQL 标签的详细介绍。

7.1 if 标签

标签用于在 SQL 语句中添加条件判断,根据条件的真假来动态生成 SQL 片段。

例如,在添加用户信息的时候,photo 字段的内容可能不确定用户是否输入,这时就需要使用 标签来构建动态 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>

需要注意的是,其中 标签中的 test 属性指定的是传入的对象的属性,而不是数据库表中的字段。

在单元测试中,只输入 usernamepassword,最后形成的 SQL 也只有这两个字段:
MyBatis 实战指南:探索灵活持久化的艺术_第39张图片
如果在增加输入一个photo 属性:

MyBatis 实战指南:探索灵活持久化的艺术_第40张图片
可以发现此时三个字段都有。

7.2 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>

其中, 标签的作用就是用于修剪插入的列名和值部分,可以在开始和结束位置删除多余的逗号。prefix 属性表示在 SQL 片段前添加的内容,suffix 属性表示在 SQL 片段后添加的内容,suffixOverrides 属性表示在 SQL 片段结尾删除的内容。

7.3 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>

另外,标签会自动去除前缀的and

当然,也可以使用 标签来实现这个功能:

<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>

此时需要使用标签,用来去除一个前缀and,以及添加一个前缀where

7.4 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>

标签和 相反,它只会去除后缀的,

7.5 foreach 标签

标签用于遍历集合或数组,并将其中的元素添加到 SQL 语句中。

标签有如下属性:

  • 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. 标签:这个标签表示一个删除语句的定义。

  2. 标签:这个标签用于遍历集合,将集合中的元素添加到 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 实战指南:探索灵活持久化的艺术_第41张图片

你可能感兴趣的:(Java,EE,mybatis,数据库)