MyBatisPlus从零到一:快速入门与核心功能详解(2)

米饭6个月前行业资讯385

二、核心功能

刚才的案例中都是以 id 为条件的简单 CRUD,一些复杂条件的 SQL 语句就要用到一些更高级的功能了。


2.1 条件构造器:

除了新增以外,修改、删除、查询的 SQL 语句都需要指定 where 条件。因此 BaseMapper 中提供的相关方法除了以id作为where条件以外,还支持更加复杂的where条件。

image-20241109094626662

参数中的Wrapper就是条件构造的抽象类,其下有很多默认实现,继承关系如图:

image-20241109094741439

Wrapper的子类AbstractWrapper提供了where中包含的所有条件构造方法:(下面的这些方法中,一般来说,第一个参数是 boolean 类型的,是用来做出动态 SQL 中 if 标签同样的效果,如果为 true,表示该条件会添加到 SQL 中,如果为 false,就不会在 SQL 中显示该条件。)

image-20241109094833251

而 QueryWrapper 在 AbstractWrapper 的基础上拓展了一个 select 方法,允许指定查询字段:

image-20241109095236012

而 UpdateWrapper 在 AbstractWrapper 的基础上拓展了一个 set 方法,允许指定 SQL 中的 SET 部分:

image-20241109095317601

2.1.1 QueryWrapper:

无论是修改、删除、查询,都可以使用 QueryWrapper 来构建查询条件。

下面举个例子:

查询:查询出名字中带o的,存款大于等于 1000 元的人的 id,username,info,balance。代码如下:

@Test
void testQueryWrapper() {
    //1. 构造查询条件
    QueryWrapper<User> queryWrapper = new QueryWrapper<User>()
            .select("id","username","info","balance")
            .like("username", "o")
            .ge("balance", 1000);
    //2. 进行查询
    List<User> users = userMapper.selectList(queryWrapper);
    users.forEach(System.out::println);
}


更新:更新用户名为 jack(Mysql 中,是不区分大小写的) 的用户的余额为 2000,代码如下:

@Test
void testUpdateByQueryWrapper(){
    //1. 构造更新条件
    //1.1 构造更新参数
    User user = new User();
    user.setBalance(2000);
    //1.2 构造更新条件
    QueryWrapper<User> queryWrapper = new QueryWrapper<User>()
            .eq("username","jack");
    //2. 进行更新
    userMapper.update(user, queryWrapper);
}

2.1.2 UpdateWrapper:

上面 BaseMapper 中的 update 方法传入 QueryWrapper 的条件更新只能直接进行复制,对于一些复杂的需求就难以实现。

例如:更新id为1,2,4的用户的余额,扣 200,对应的 SQL 应该是:

UPDATE user SET balance = balance - 200 WHERE id in (1, 2, 4)

SET 的赋值结果是基于字段现有值的,这个时候就要利用 UpdateWrapper 中的 setSql 功能了:

@Test
void testUpdateWrapper(){
    //1. 构造更新条件
    UpdateWrapper<User> updateWrapper = new UpdateWrapper<User>()
            .setSql("balance = balance - 200")
            .in("id",List.of(1L,2L,4L));
    //2. 进行更新
    userMapper.update(null, updateWrapper);
}

2.1.3 LambdaQueryWrapper || LambdaUpdateWrapper:

无论是 QueryWrapper 还是 UpdateWrapper 在构造条件的时候都需要写死字段名称,这在编程规范中显然是不推荐的。


那怎么样才能不写字段名,又能知道字段名呢?


一种办法是基于变量的gettter方法结合反射技术。因此我们只要将条件对应的字段的getter方法传递给 MybatisPlus,它就能计算出对应的变量名,从而知道字段名。而传递方法可以使用 JDK8 中的方法引用和Lambda表达式。 因此 MybatisPlus 又提供了一套基于Lambda 的 Wrapper ,包含两个:


LambdaQueryWrapper

LambdaUpdateWrapper

分别对应 QueryWrapper 和 UpdateWrapper。


LambdaQueryWrapper 的使用演示如下:

@Test
void testLambdaQueryWrapper() {
    //1. 构造查询条件
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<User>()
            .select(User::getId, User::getUsername, User::getInfo, User::getBalance)
            .like(User::getUsername, "o")
            .ge(User::getBalance, 1000);
    //2. 进行查询
    List<User> users = userMapper.selectList(lambdaQueryWrapper);
    users.forEach(System.out::println);
}

LambdaUpdateWrapper 的使用演示如下:(需要注意的是,如果是使用 setSql 的话,是不能使用 Lambda 表达式的,如果需要达成在原来的基础上进行扣减,且不使用 setSql 的话,可以从 Java 代码的业务逻辑出发,先查出当前 user 的信息,在对该 user 的余额进行扣减,再更新回去即可,实现起来并不难,所以下面演示的代码就简单的进行赋值)。

@Test
void testLambdaUpdateWrapper() {
    // 1. 构造更新条件
    LambdaUpdateWrapper<User> lambdaUpdateWrapper = new LambdaUpdateWrapper<User>()
            .set(User::getBalance, 1000)
            .in(User::getId, List.of(1L, 2L, 3L));
    //2. 进行更新
    userMapper.update(null, lambdaUpdateWrapper);
}

2.2 自定义 SQL:

在演示 UpdateWrapper 的案例中,我们在代码中编写了更新的 SQL 语句:

image-20241109110422232

这种写法在某些企业也是不允许的,因为 SQL 语句最好都维护在持久层,而不是业务层。 就当前案例来说,由于条件是 in 语句,只能将SQL 写在 Mapper.xml 文件,利用 foreach 来生成动态 SQL。 这实在是太麻烦了。假如查询条件更复杂,动态 SQL 的编写也会更加复杂。


所以,MybatisPlus 提供了自定义 SQL 功能,可以让我们利用 Wrapper 生成查询条件,再结合 Mapper.xml 编写 SQL。这样的做法算是综合了一下利弊,将 where 之前的 sql 语句放在 Mapper 层,进行编写,where 后面的语句在业务层进行编写,虽然还是不符合规范,但是较之前全部 sql 都放在业务层,也算是有了提升,MybatisPlus 的优势在于编写 where 条件非常的方便,使用 xml 文件进行动态 sql 编写,太繁琐了。


2.2.1 基本用法:

@Test
void testCustomWrapper(){
    //1. 构造 Wrapper 条件
    QueryWrapper<User> queryWrapper = new QueryWrapper<User>()
            .in("id", List.of(1L, 2L, 4L));
    //2. 进行扣除余额
    userMapper.deductBalanceByIds(queryWrapper,200);
}
// Mapper 层
void deductBalanceByIds(@Param("ew") QueryWrapper<User> queryWrapper, @Param("balance") int balance);
// xml 文件
<update id="deductBalanceByIds">
        update user set balance = balance - #{balance} ${ew.customSqlSegment}
</update>

注意:@Param(“ew”) QueryWrapper queryWrapper,中 @Param(“ew”) 是固定写法,${ew.customSqlSegment} 也是固定写法。


2.2.2 多表关联:

理论上来讲 MyBatisPlus 是不支持多表查询的,不过我们可以利用 Wrapper 中自定义条件,结合自定义 SQL 来实现多表查询的效果。例如,我们要查询出所有收货地址在北京的并且用户 id 在1、2、4之中的用户,要是自己基于 mybatis 实现 SQL,大概是这样的:

<select id="queryUserByIdAndAddr" resultType="User">
      SELECT *
      FROM user u
      INNER JOIN address a ON u.id = a.user_id
      WHERE u.id
      <foreach collection="ids" separator="," item="id" open="IN (" close=")">
          #{id}
      </foreach>
      AND a.city = #{city}
  </select>

可以看出其中 where 的编写还是挺复杂的,如果业务复杂一些,这里的 SQL 会更变态。

但是基于自定义 SQL 结合 Wrapper 的玩法,我们就可以利用 Wrapper 来构建查询条件,然后手写 SELECT 及 FROM 部分,实现多表查询。

对应的代码如下:

// VO 实体类
@Data
public class UserVO extends User {
    private String city;
}
// Test 测试类
@Test
void testCustomJoinWrapper(){
    //1. 构造 Wrapper 条件
    QueryWrapper<UserVO> queryWrapper = new QueryWrapper<UserVO>()
        .in("u.id",List.of(1L,2L,4L))
        .eq("a.city","北京");
    //2. 进行查询
    List<UserVO> users = userMapper.queryUserAndAddressByWrapper(queryWrapper);
}
// Mapper 层代码
List<UserVO> queryUserAndAddressByWrapper(@Param("ew") QueryWrapper<UserVO> queryWrapper); 

// xml 文件中的对应代码
<select id="queryUserAndAddressByWrapper" resultType="com.gobeyye.mp.domain.vo.UserVO">
    select u.*,a.city from user u inner join address a
    on u.id = a.user_id ${ew.customSqlSegment}
</select>

对于上面这段 Mybatis plus 使用自定义 SQL 写多表查询,我还是更加建议使用原来的 Mybatis,配合使用 AI ,也不会太繁琐,且代码的可读性更好,不用一条 SQL 多个文件里面去找。

本文系转载,版权归原作者所有,如若侵权请联系我们进行删除!  

云掣基于多年在运维领域的丰富时间经验,编写了《云运维服务白皮书》,欢迎大家互相交流学习:

《云运维服务白皮书》下载地址:https://fs80.cn/v2kbbq

想了解更多大数据运维托管服务、数据库运维托管服务、应用系统运维托管服务的的客户,欢迎点击云掣官网沟通咨询:https://yunche.pro/?t=shequ


相关文章

Spring AOP 实战指南:从入门到精通(1)

Spring AOP 实战指南:从入门到精通(1)

Spring 框架有两大核心 IoC,AOP。在前面我们已经学习过了 IoC 的相关知识,今天就让我们开始 AOP 的学习。一、AOP 概述Aspect Oriented Programming(面向...

Docker:技术架构的演进之路(上)

Docker:技术架构的演进之路(上)

前言一、前言技术架构是指在软件开发和系统构建中,为了满足业务需求和技术要求,对系统的整体结构、组件、接口、数据流以及技术选型等方面进行的详细设计和规划。它是软件开发过程中的重要组成部分,为开发团队提供...

Spring AMQP与RabbitMQ深度整合指南:从基础到高级应用(1)

Spring AMQP与RabbitMQ深度整合指南:从基础到高级应用(1)

一、初识 MQ1.1 同步调用:我们观察下,下面这个余额支付功能的流程图:如果我们采用的是基于 OpenFeign 的同步调用,也就是说业务执行流程是这样的:支付服务需要先调用用户服务完成余额扣减。然...

如何为阿里云服务器配置域名

如何为阿里云服务器配置域名

域名是互联网上的门牌号,它将用户输入的网址转换为服务器的IP地址,从而让用户能够访问到服务器上托管的网站或应用。阿里云作为国内领先的云服务提供商,提供了强大的服务器和域名管理服务。本文将详细介绍如何为...

SRE(站点可靠性工程)介绍

SRE(站点可靠性工程)介绍

概述站点可靠性工程(SRE)是 IT 运维的软件工程方案。SRE 团队使用软件作为工具,来管理系统、解决问题并实现运维任务自动化。SRE 执行的任务以前通常由运维团队手动执行,或者交给使用软件和自动化...

浅谈linux性能调优之六:IO调度算法的选择

浅谈linux性能调优之六:IO调度算法的选择

一) I/O调度程序的总结     1) 当向设备写入数据块或是从设备读出数据块时,请求都被安置在一个队列中等待完成.    2) 每个块设备都有它自...

发表评论    

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。