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

米饭20小时前行业资讯8

二、核心功能

刚才的案例中都是以 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


相关文章

解决虚拟机卡死且无法强制结束进程的问题

解决虚拟机卡死且无法强制结束进程的问题

【Linux虚拟机死机】有很多种情况,最常见的是系统负载过高导致的。可以是运行内存耗用极大的程序,也会迅速提升系统负载。由于系统负载过高导致的卡死,一定是解决的越快越好!不能再试图依赖任何图形界面的东...

在经济低迷时管理云服务的策略!

近几年全球经济在疫情等各方面影响之下持续低迷,Wanclouds公司发布的一份研究报告指出,81%的美国IT领导者表示,他们的首席执行官要求他们减少或者不增加云计算支出。事实上,在那些被要求削减成本的...

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

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

3.5 声明交换机和队列:在之前我们都是基于RabbitMQ控制台来创建队列、交换机。但是在实际开发时,队列和交换机是程序员定义的,将来项目上线,又要交给运维去创建。那么程序员就需要把程序中运行的所有...

【计算机网络】详解数据链路层数据帧&Mac地址&ARP协议

【计算机网络】详解数据链路层数据帧&Mac地址&ARP协议

一、以太网帧         "以太网" 不是一种具体的网络,而是一种技术标准;既包含了数据链路层的内容,也包含了一些物理层的内容...

Docker 基础与实战指南(4)

Docker 基础与实战指南(4)

2.4 网络:默认情况下,所有容器都是以 bridge 方式连接到 Docker 的一个虚拟网桥上:容器在同一个网桥上就可以相互访问。下图就是我的 linux 上的默认网桥。下面我们来测试一下。首先,...

【Docker 】提升开发效率的 Docker 实践技巧

【Docker 】提升开发效率的 Docker 实践技巧

在现代软件开发中,Docker 已成为重要的工具之一。本文将介绍如何利用 Docker 的构建缓存、自定义镜像、私有镜像仓库,以及环境变量来优化开发流程。一、构建缓存合理安排 Dockerfile 中...

发表评论    

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