普通视图

Received before yesterday

MybatisPlus(二)核心功能03 — IService接口

作者陶其
2025年3月5日 20:54

感谢订阅陶其的个人博客!

视频课程地址:黑马商城项目

上一节:MybatisPlus(二)核心功能02 — 自定义SQL

本节学习MybatisPlus(MP)的核心功能 — IService接口:

  • IService接口基本用法
  • IService开发基础业务接口
  • IService开发复杂业务接口
  • IService的lambda方法
  • IService批量新增

一、IService 接口基本用法

1.1 IService接口

1.1.1 新增接口

  • save(T) :保存一个实体类。
  • saveBatch(Collection<T>) :批量新增实体对象集合。
  • saveBatch(Collection<T>, int) :批量新增实体对象集合,按照第二个参数分批次新增。
    • 例如:集合中有90个实体对象,第二个参数为20,那么会分为5次进行保存,前4次每次20个,最后一次10个。
  • saveOrUpdateBatch(Collection<T>) :批量新增或修改实体对象集合。
    • 传入的参数会判断是否存在id参数,如果有就会认为是更新操作;如果没有就是新增操作。
  • saveOrUpdateBatch(Collection<T>, int) :分批次,批量新增或修改实体对象集合。效果同上。
  • saveOrUpdate(T) :新增或修改实体对象。
  • saveOrUpdate(T, Wrapper<T>) :新增或修改实体对象,第二个参数定义查询条件,不再是默认id。

1.1.2 删除接口

  • removeById(Serializable) :根据ID删除数据。
  • removeById(Serializable, boolean) :根据ID删除数据。第二个参数是控制逻辑删除或者物理删除。
    • 第二个boolean参数:true:逻辑删除,更新配置的[逻辑删除字段]为[逻辑删除值];false:物理删除。
  • removeById(T) :识别实体对象的主键进行删除。
  • removeByMap(Map<String, Object>) :根据给定的条件映射来构建WHERE子句删除数据。
  • remove(Wrapper<T>) :根据构造条件给定的WHERE子句删除数据。
  • removeByIds(Collection<?>) :根据主键集合批量删除数据。(适合主键集合元素较少时使用,采用DELETE+ID IN
  • removeByIds(Collection<?>, boolean) :根据主键集合批量删除数据。第二个参数控制true(逻辑删除)/false(物理删除)
  • removeBatchByIds(Collection<?>) :根据主键集合分批次批量删除数据。(适合主键集合元素较多时使用,采用DELETE+ID=? ,会采用JDBC批处理方案)
  • removeBatchByIds(Collection<?>, boolean) :根据主键集合分批次批量删除数据。第二个参数效果同上。
  • removeBatchByIds(Collection<?>, int) :根据主键集合分批次批量删除数据。第二个元素控制每批次数量。
  • removeBatchByIds(Collection<?>, int, boolean) :根据主键集合分批次批量删除数据。第二个元素控制每批次数量。第三个参数控制true(逻辑删除)/false(物理删除)。

1.1.3 修改接口

  • 前两个和最后一个就不说了,新增接口时已经介绍了
  • updateById(T) :根据ID进行修改,实体对象只要非空就会修改。
  • update(Wrapper<T>) :根据构造条件进行修改,实体对象只要非空就会修改。
  • update(T, Wrapper<T>) :根据构造条件对实体对象进行修改。
  • updateBatchById(Collection<T>) :根据主键ID批量修改。
  • updateBatchById(Collection<T>, int) :根据主键ID分批次批量修改,第二个参数定义每批次数量。

1.1.4 查询接口(查一个)

  • getById(Serializable) :根据主键ID查询。
  • getOne(Wrapper<T>) :根据构造条件查询。需要保证构造条件只能查到一个,否则会异常。
  • getOne(Wrapper<T>, boolean) :根据构造条件查询。第二个参数:控制异常抛出
    • 如果查询到不止一条数据,正常程序会抛出TooManyResultsException异常。
    • true :会抛出异常;
    • false :不会抛出异常,会返回第一条记录。

1.1.5 查询接口(查统计)

  • count() :查询统计总数
  • count(Wrapper<T>) :根据构造条件查询统计总数

1.1.6 查询接口(查多个/列表)

  • listByIds(Collection<? extends Serializable>) :根据ID集合查询。
  • listByMap(Map<String, Object>) :根据给定的条件映射作为 WHERE 条件查询
  • list(Wrapper<T>) :根据构造条件查询
  • list() :查询所有数据

1.1.7 查询接口(分页查询)

  • page(E, Wrapper<T>) :分页查询。
    • E :分页对象,通常需要实现IPage 接口,常用Page 类,查询结果也会封装到这个对象中。
    • Wrapper<T> :构造查询条件
  • page(E) :没有查询条件,直接分页查询。

1.1.8 Lambda接口

基于这种Lambda的接口,可以直接使用链式编程方式,不用自己去 new 一个 Wrapper

建议:

如果是基于ID等的简单查询或更新就用前面的方法;

如果是复杂查询,那么可以使用lambda进行链式编程查询。

  • lambdaQuery() :创建一个基于 Lambda 表达式的查询条件构造器 LambdaQueryWrapper
  • lambdaQuery(T) :创建基于 Lambda 表达式的查询条件构造器。构造器会根据实体对象中不为 null 的属性自动生成等值查询条件。
  • lambdaUpdate() :创建一个基于 Lambda 表达式的更新条件构造器 LambdaUpdateWrapper

1.2 IService的使用

继承和实现逻辑:

1.2.1 创建自定义Service接口

自定义接口继承IService 即可,需要给出 User 的泛型。

package com.itheima.mp.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.mp.domain.po.User;

public interface IUserService extends IService<User> {

}

1.2.2 创建自定义Service实现类

自定义接口实现类,实现自定义接口UserService ,并继承ServiceImpl 类并给出两个泛型:UserMapperUser

ServiceImpl 通过UserMapper 调用它继承的BaseMapper 中的方法。

package com.itheima.mp.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.mp.domain.po.User;
import com.itheima.mp.mapper.UserMapper;
import com.itheima.mp.service.IUserService;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

}

1.2.3 测试 – 单个新增

package com.itheima.mp.service;

import com.itheima.mp.domain.po.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.time.LocalDateTime;

@SpringBootTest
class IUserServiceTest {

    @Autowired
    private IUserService userService;

    @Test
    void testSaveUser() {
        User user = new User();
        user.setId(6L);
        user.setUsername("李白");
        user.setPassword("123456");
        user.setPhone("18688990012");
        user.setBalance(10086);
        user.setInfo("{\"age\": 1324, \"intro\": \"诗词老师\", \"gender\": \"male\"}");
        user.setCreateTime(LocalDateTime.now());
        user.setUpdateTime(LocalDateTime.now());
        userService.save(user);
    }
}

这里测试了一个 save(Entity) 的接口方法。

运行结果:

​ 成功添加数据。

1.2.4 测试 – 查询列表

    @Test
    void testQueryByIds() {
        List<Long> idList = List.of(1L, 3L, 6L);
        List<User> users = userService.listByIds(idList);
        users.forEach(System.out::println);
    }

运行结果:

1.3 总结

二、IService 开发基础业务接口

2.1 通过案例学习

本节只学习1-4项,第五项为第三节学习,为复杂业务接口。

2.2 引入依赖

因为我们需要实现Restful风格的接口,所以需要引入web依赖。

然后为了方便测试,我们使用swagger进行测试接口,然后需要引入swagger依赖。

pom.xml中加入如下依赖:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
            <version>4.1.0</version>
        </dependency>

2.3 做swagger的配置

在application.yml文件中新增:

knife4j:
  enable: true # 开启
  openapi:
    title: 用户管理接口文档
    description: "用户管理接口文档"
    email: 2451203736@qq.com
    concat: 陶其
    url: https://www.tqazy.com
    version: v1.0.0
    group:
      default:
        group-name: default
        api-rule: package
        api-rule-resources:
          - com.itheima.mp.controller

顺便将mybatis的配置改成mybatis-plus的:

mybatis-plus:
  type-aliases-package: com.tiheima.mp.domain.po
  global-config:
    db-config:
      id-type: auto # ID自增

2.4 导入dto文件和vo文件

UserFormDTO.java

package com.itheima.mp.domain.dto;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

@Data
@ApiModel(description = "用户表单实体")
public class UserFormDTO {

    @ApiModelProperty("id")
    private Long id;

    @ApiModelProperty("用户名")
    private String username;

    @ApiModelProperty("密码")
    private String password;

    @ApiModelProperty("注册手机号")
    private String phone;

    @ApiModelProperty("详细信息,JSON风格")
    private String info;

    @ApiModelProperty("账户余额")
    private Integer balance;
}

UserVo

package com.itheima.mp.domain.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

@Data
@ApiModel(description = "用户VO实体")
public class UserVO {

    @ApiModelProperty("用户id")
    private Long id;

    @ApiModelProperty("用户名")
    private String username;

    @ApiModelProperty("详细信息")
    private String info;

    @ApiModelProperty("使用状态(1正常 2冻结)")
    private Integer status;

    @ApiModelProperty("账户余额")
    private Integer balance;
}

2.5 创建并编写控制层

UserController.java

import cn.hutool.core.bean.BeanUtil;
import com.itheima.mp.domain.dto.UserFormDTO;
import com.itheima.mp.domain.po.User;
import com.itheima.mp.domain.vo.UserVO;
import com.itheima.mp.service.IUserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;

@Api(tags = "用户管理接口")
@RequestMapping("/users")
@RestController
@RequiredArgsConstructor
public class UserController {

    private final IUserService userService;

    @ApiOperation("新增用户接口")
    @PostMapping
    public void saveUser(@RequestBody UserFormDTO userDTO) {
        // 1. 把DTO拷贝到PO
        User user = BeanUtil.copyProperties(userDTO, User.class);
        // 2. 新增
        userService.save(user);
    }

    @ApiOperation("删除用户接口")
    @DeleteMapping("{id}")
    public void deleteUserById(@ApiParam("用户id") @PathVariable("id") Long id) {
        userService.removeById(id, true);
    }

    @ApiOperation("根据id查询用户")
    @GetMapping("{id}")
    public UserVO getUserById (@ApiParam("用户id") @PathVariable("id") Long id) {
        User user = userService.getById(id);
        return BeanUtil.copyProperties(user, UserVO.class);
    }

    @ApiOperation("根据id批量查询")
    @GetMapping
    public List<UserVO> getAllUser(@ApiParam("用户id集合") @RequestParam("idList") List<Long> idList) {
        List<User> users = userService.listByIds(idList);
        return BeanUtil.copyToList(users, UserVO.class);
    }
}

代码注解:

  • @RequiredArgsConstructor : 配合private final IUserService userService;final,可以实现必须的依赖通过构造函数注入,必须的依赖使用final关键字修饰。比@Resources更推荐使用,@Resources是JavaEE的注解,更推荐Spring的注解。

2.6 创建并编写逻辑层

IUserService.java

public interface IUserService extends IService<User> {

}

UserServiceImpl.java

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

}

2.7 创建并编写持久层

UserMapper.java

public interface UserMapper extends BaseMapper<User>{

}

UserMapper.xml

<?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.itheima.mp.mapper.UserMapper">

</mapper>

2.8 运行

启动项目,打开浏览器访问:127.0.0.1:8080/doc.html。

2.8.1 新增用户

2.8.2 根据id查询用户

2.8.3 根据id批量查询

2.8.4 删除用户

三、IService开发复杂业务接口

将上面已删除的数据再加回。

3.1 控制层

UserController.java

    @ApiOperation("根据id扣减余额")
    @PostMapping("/{id}/deduction/{money}")
    public String deductionMoneyById(@ApiParam("用户id") @PathVariable("id") Long id, @ApiParam("扣减金额") @PathVariable("money") int money) {
        return userService.deductionMoneyById(id, money);
    }

3.2 逻辑层

IUserService.java

public interface IUserService extends IService<User> {

    String deductionMoneyById(Long id, int money);

}

UserServiceImpl.java

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    @Resource
    private UserMapper userMapper;

    @Override
    public String deductionMoneyById(Long id, int money) {
        if(id != null) {
            User user = getById(id); // 调的是ServiceImpl的方法
            if (user != null) {
                if (!user.getStatus().equals(1)) {
                    return "用户状态为非正常状态,扣款失败!";
                } else if (user.getBalance() <= money) {
                    return "用户余额不足,扣款失败!现有余额为:" + user.getBalance();
                } else {
                    userMapper.deductionMoneyById(id, money);
                    user = getById(id);
                    return "扣款成功!现有余额为:" + user.getBalance();
                }
            } else {
                return "查询用户不存在!";
            }
        } else {
            return "参数ID为空!";
        }
    }
}

3.3 持久层

UserMapper.java

    @Update("UPDATE user SET balance = balance - #{money} WHERE id = #{id}")
    void deductionMoneyById(@Param("id") Long id, @Param("money") int money);

3.4 运行

四、IService 的 lambda 方法

4.1 lambda的查询方法

如果按照Mybatis的写法,需要在UserMapper.xml写如下SQL语句:

    <select id="queryUsers" resultType="com.itheima.mp.domain.po.User">
        SELECT *
        FROM user
        <where>
            <if test="name != null">
                AND username LIKE #{name}
            </if>
            <if test="status != null">
                AND `status` = #{status}
            </if>
            <if test="minBalance != null">
                AND balance >= #{minBalance}
            </if>
            <if test="maxBalance != null">
                AND balance <= #{maxBalance}
            </if>
        </where>
    </select>

4.1.1 导入查询条件实体类

UserQuery.java

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

@Data
@ApiModel(description = "用户查询条件实体")
public class UserQuery {
    @ApiModelProperty("用户名关键字")
    private String name;
    @ApiModelProperty("用户状态:1-正常,2-冻结")
    private Integer status;
    @ApiModelProperty("余额最小值")
    private Integer minBalance;
    @ApiModelProperty("余额最大值")
    private Integer maxBalance;
}

4.1.2 控制层

UserController.java

    @ApiOperation("根据条件批量查询")
    @GetMapping("/list")
    public List<UserVO> queryUsers(@ApiParam("用户查询条件") UserQuery query) {
        List<User> users = userService.queryUsers(
                query.getName(), query.getStatus(), query.getMinBalance(), query.getMaxBalance());
        return BeanUtil.copyToList(users, UserVO.class);
    }

GET 请求接收参数可以直接使用实体类接收,无需加注解。

4.1.3 逻辑层

IUserService.java

List<User> queryUsers(String name, Integer status, Integer minBalance, Integer maxBalance);

UserServiceImpl.java

    @Override
    public List<User> queryUsers(String name, Integer status, Integer minBalance, Integer maxBalance) {
        return lambdaQuery()
                .like(name != null, User::getUsername, name)
                .eq(status != null, User::getStatus, status)
                .ge(minBalance != null, User::getBalance, minBalance)
                .le(maxBalance != null, User::getBalance, maxBalance)
                .list();
    }

代码解析:

  • lambdaQuery()default LambdaQueryChainWrapper<T> lambdaQuery(),链式查询 lambda 式。
  • .like(name != null, User::getUsername, name) :模糊查询,第一参数为true时此项才算入查询条件;
  • .eq(status != null, User::getStatus, status) :精准查询,第一参数效果同上;
  • .ge(minBalance != null, User::getBalance, minBalance) :字段值大于等于参数值 查询,第一参数效果同上;
  • .le(maxBalance != null, User::getBalance, maxBalance) :字段值小于等于参数值 查询,第一参数效果同上;
  • .list() :查询列表,返回 List<T>。还可以选:
    • one():查询一条记录,返回 <T>
    • count():查询统计条数,返回 Long
    • page() :分页查询,返回 E page
    • exists():查询是否存在数据,返回 boolean

4.1.4 运行

未完待续。。。

喜欢MybatisPlus(二)核心功能03 — IService接口这篇文章吗?您可以点击浏览我的博客主页 发现更多技术分享与生活趣事。

MybatisPlus(二)核心功能02 — 自定义SQL

作者陶其
2025年3月5日 18:50

感谢订阅陶其的个人博客!

视频课程地址:黑马商城项目

上一节:MybatisPlus(二)核心功能01 — 条件构造器
下一节:MybatisPlus(二)核心功能03 — IService接口
本节学习MybatisPlus(MP)的核心功能 — 自定义SQL:

  • 在业务层拼接SQL的问题
  • 自定义SQL的用法

一、自定义SQL简介

自定义SQL:我们可以利用MyBatisPlus的Wrapper来构建复杂的Where条件,然后自己定义SQL语句中剩下的部分。

二、问题出现

下面的案例是完全手写SQL的方式。

上一节中我们遇到了相似的案例,使用了 LambdaUpdateWrapper ,写法如下:

    @Test
    void testLambdaUpdateWrapper() {
        List<Long> idList = List.of(1L, 2L, 4L);
        LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<User>()
                .setSql(true, "balance = (balance - 200)")
                .in(User::getId, idList);

        userMapper.update(null, wrapper);
    }

而上面的代码是有问题的

代码中 .setSql() 部分是写的SQL语句,但是这整段代码一般情况下是在service层的,也就是业务逻辑层。

而在业务逻辑层代码拼写应该在持久层出现的SQL语句,这是一种很不规范的写法。

那么怎么办呢?

那就将复杂的SQL拼写分为两个部分:

  • 一部分可以由MP直接构建的,交给MP
  • 另一部分相对复杂,MP无法构建的就自定义SQL(手写SQL)

当然这个手写的SQL不是在业务层进行拼写,而是在Mapper层。

只要将业务层中MP构建的传递到Mapper层和自定义SQL组装起来就可以了。

三、自定义SQL用法

用上面的代码进行改写:

3.1 基于Wrapper构建where条件

模拟在业务层进行MP构建:

    @Test
    void testCustomSqlSegment() {
        List<Long> idList = List.of(1L, 2L, 4L);
        int amount = 200;
        // 1. 构建条件
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>().in(User::getId, idList);
        // 2. 自定义SQL方法调用
        userMapper.updateBalanceByIds(wrapper, amount);
    }

3.2 Mapper.java中自定义方法

在mapper层的方法参数中用 @Param 注解声明wrapper变量名称,必须是ew

void updateBalanceByIds(@Param("ew")LambdaQueryWrapper<User> wrapper, @Param("amount") int amount);

3.3 Mapper.xml中自定义SQL

    <update id="updateBalanceByIds">
        UPDATE user SET balance = balance - #{amount} ${ew.customSqlSegment}
    </update>

代码解析:

  • ${ew.customSqlSegment}: 会拼接MP构建好的SQL片段

运行结果:

​ 成功修改数据。

3.4 总结

通过这个方式,我们成功将MP构建放在了业务层,把自定义SQL放在了Mapper层。分层正确。

使用场景:

​ SQL语句,where条件之外的部分,我们无法通过MP更方便的实现,只能用拼接。

​ 那么为了不违背编写规范,采用这种方式:where条件使用MP构建,其余部分进行自定义SQL。

喜欢MybatisPlus(二)核心功能02 — 自定义SQL这篇文章吗?您可以点击浏览我的博客主页 发现更多技术分享与生活趣事。

MybatisPlus(二)核心功能01 — 条件构造器

作者陶其
2025年3月5日 16:17

感谢订阅陶其的个人博客!

视频课程地址:黑马商城项目

上一节:MybatisPlus(一)快速入门
下一节:MybatisPlus(二)核心功能02 — 自定义SQL
本节学习MybatisPlus(MP)的核心功能 — 条件构造器:

  • Wrapper
  • AbstractWrapper详解
  • QueryWrapperLambdaQueryWrapper,以及相关示例
  • UpdateWrapperLambdaUpdateWrapper,以及相关示例

一、Wrapper

MyBatisPlus支持各种复杂的where条件,可以满足日常开发的所有需求。

Wrapper:条件构造器。用于构造复杂SQL语句的。

1.1 Wrapper的继承关系

二、AbstractWrapper详解

以下是 AbstractWrapper 中一些常用方法的介绍:

2.1 实体信息操作方法

  • getEntity(): T:用于获取当前条件构造器所关联的实体对象
  • setEntity(T): Children:用于设置当前条件构造器所关联的实体对象
  • getEntityClass(): Class<T>:用于获取当前条件构造器所关联的实体类的 Class 对象
  • setEntityClass(Class<T>): Children:用于设置当前条件构造器所关联的实体类的 Class 对象

2.2 等值比较方法

  • eq(boolean, R, Object):Children:用于构建等于(=)条件。
    • 第一个布尔参数用于控制是否添加该条件,第二个参数为数据库字段,第三个参数为要比较的值。
  • ne(boolean, R, Object):Children:用于构建不等于(<>)条件。逻辑同 eq 方法。
  • gt(boolean, R, Object):Children:用于构建大于(>)条件。逻辑同 eq 方法。
  • ge(boolean, R, Object):Children:用于构建大于等于(>=)条件。逻辑同 eq 方法。
  • lt(boolean, R, Object):Children:用于构建小于(<)条件。逻辑同 eq 方法。
  • le(boolean, R, Object):Children:用于构建小于等于(<=)条件。逻辑同 eq 方法。

2.3 多条件等值匹配方法

  • allEq(boolean, Map<R, V>, boolean): Children:当第一个布尔参数为 true 时,根据 Map 中的键值对构建多个等值条件,第三个布尔参数控制对 Map 中值为 null 的键值对的处理方式,若为 true 则构建 IS NULL 条件,否则忽略该键值对
  • allEq(boolean, BiPredicate<R, V>, Map<R, V>, boolean): Children:当第一个布尔参数为 true 时,根据 Map 中的键值对构建多个等值条件,第二个 BiPredicate 参数用于对键值对进行过滤,只有满足 BiPredicate 条件的键值对才会用于构建条件,第四个布尔参数控制对 Map 中值为 null 的键值对的处理方式

2.4 范围比较方法

  • between(boolean, R, Object, Object):Children:用于构建 BETWEEN...AND... 范围条件。
    • 第一个布尔参数控制是否添加条件,第二个参数为数据库字段,后两个参数分别为范围的起始和结束值。
  • notBetween(boolean, R, Object, Object):Children:用于构建 NOT BETWEEN...AND... 范围条件。逻辑同 between 方法。

2.5 模糊查询方法

  • like(boolean, R, Object):Children:用于构建 LIKE 模糊查询条件,会在值前后自动添加 %
  • notLike(boolean, R, Object):Children:用于构建 NOT LIKE 模糊查询条件。逻辑同 like 方法。
  • likeLeft(boolean, R, Object):Children:用于构建左模糊查询条件,会在值前添加 %
  • likeRight(boolean, R, Object):Children:用于构建右模糊查询条件,会在值后添加 %

2.6 范围查询方法

  • between(boolean, R, Object, Object): Children:当布尔参数为 true 时,用于构建范围(BETWEEN...AND...)条件
  • notBetween(boolean, R, Object, Object): Children:当布尔参数为 true 时,用于构建不在某个范围(NOT BETWEEN...AND...)条件

2.7 模糊查询否定方法

  • notLikeLeft(boolean, R, Object): Children:当布尔参数为 true 时,用于构建左模糊不匹配(NOT LIKE '%值')条件
  • notLikeRight(boolean, R, Object): Children:当布尔参数为 true 时,用于构建右模糊不匹配(NOT LIKE '值%')条件

2.8 空值判断方法

  • isNull(boolean, R):Children:用于构建字段为空(IS NULL)的条件。
  • isNotNull(boolean, R):Children:用于构建字段不为空(IS NOT NULL)的条件。

2.9 逻辑组合方法

  • and(Consumer<Children>):Children:用于添加 AND 逻辑的子条件。
  • or():Children:用于简单的 OR 逻辑连接。
  • or(Consumer<Children>):Children:用于添加带括号的 OR 逻辑子条件。

2.10 排序方法

  • orderByAsc(R...):Children:用于按指定字段进行升序排序。
  • orderByDesc(R...):Children:用于按指定字段进行降序排序。

2.11 分组和聚合方法

  • groupBy(R...):Children:用于按指定字段进行分组查询。
  • having(String, Object...):Children:用于添加 HAVING 子句,可用于对分组结果进行过滤。

三、QueryWrapper

在父类基础上,扩展了select功能,允许在构造SQL语句时可以指定select哪些字段。

  • select(String...):参数传需要的字段即可,多个字段使用,间隔。例如:select("id", "username", "info", "balance")
  • select(List<String>):参数传需要的字段的List集合。

四、UpdateWrapper

在父类基础上,扩展了set部分。

红框内的方法是使用字符串的形式把set部分写出来,最后拼到SQL语句中。(比较少见)

  • setSql(boolean, String)
    • 第一个参数:true(后面的SQL语句拼接到SQL语句中)、false(后面SQL语句会被忽略,不被使用)
    • 第二个参数:set部分的SQL。例如:“

五、使用Lambda语法子类(推荐)

  • AbstractLambdaaWrapper
    • LambdaUpdateWrapper
    • LambdaQueryWrapper

这三个和上面三个作用其实相同,只是扩展了可以使用lambda的写法。

为什么要用Lambda语法?

因为如下方6.3中的查询代码一样,将需要查询的字段名直接硬编码到代码中,这种方式是不推荐的。

LambdaWrapper就是解决这个问题的。

具体怎么使用看下面的第八节

六、案例一:基于QueryWrapper的查询

6.1 案例

红色的SQL语句是我针对这个案例写出的SQL,下面就要使用MP构造相关SQL。

6.2 UserMaper

依旧不用写任何方法

public interface UserMapper extends BaseMapper<User>{

}

6.3 单元测试代码(①)

    @Test
    void testQueryWrapper() {
        // 1. 构建查询条件
        QueryWrapper<User> wrapper = new QueryWrapper<User>()
                .select("id", "username", "info", "balance")
                .like("username", "o")
                .ge("balance", "1000");

        // 2. 查询
        List<User> users = userMapper.selectList(wrapper);
        users.forEach(System.out::println);
    }

运行结果:

代码解析:

  • 构建查询条件(wrapper)时采用链式编程方式。

  • new QueryWrapper<User>:创建QueryWrapper类的实例,并使用方法对构造条件进行丰富,同时传入User为泛型

  • .select("id", "username", "info", "balance"):构建SQL的select部分,指定需要查询的字段

  • .like("username", "o"):构造where部分的条件:模糊查询username字段值带有o字符串的

  • .ge("balance", "1000"):构造where部分的条件:balance字段大于等于1000的

  • 如上的构造条件拼接成SQL即为:

    • select id, username, info, balance 
      from user
      where username like CONCAT('%', 'o' ,'%') 
      AND balance >= 1000;
  • List<User> users = userMapper.selectList(wrapper);:使用UserMapper利用这个构造查询并返回User的列表

1.6.4 单元测试代码(②)

    @Test
    void testUpdateByQueryWrapper() {
        // 1. 要更新的数据
        User user = new User();
        user.setBalance(2000);

        // 2. 更新的条件
        QueryWrapper<User> wrapper = new QueryWrapper<User>()
                .eq("username", "jack");

        // 3. 执行更新
        userMapper.update(user, wrapper);
    }

运行结果:

​ 已成功更新数据。

代码解析:

  • User user = new User();user.setBalance(2000);:先将需要更新的数据(set部分字段)写成对象

  • QueryWrapper<User> wrapper = new QueryWrapper<User>().eq("username", "jack");:再构建SQL语句的WHERE条件

  • userMapper.update(user, wrapper):最后执行更新

七、案例二:基于UpdateWrapper的更新

7.1 案例

7.2 单元测试代码

    @Test
    void testUpdateWrapper() {
        List<Long> idList = List.of(1L, 2L, 3L);
        UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
                .setSql(true, "balance = (balance - 200)")
                .in("id", idList);

        userMapper.update(null, wrapper);
    }

运行结果:

​ 已成功更新数据。

代码解析:

  • UpdateWrapper<User> wrapper = new UpdateWrapper<User>():创建一个UpdateWrapper实例
  • .setSql(true, "balance = (balance - 200)"):手写set部分的代码(不要set关键字)
  • .in("id", idList):这是WHERE部分的筛选条件
  • userMapper.update(null, wrapper):执行修改方法,因为不需要传入User实体类作为修改对象,所以直接传null

八、基于LambdaWrapper查询

8.1 重构6.3的代码

    @Test
    void testLambdaQueryWrapper() {
        // 1. 构建查询条件
        LambdaQueryWrapper<User> wrapper = 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(wrapper);
        users.forEach(System.out::println);
    }

运行结果与之前的代码效果相同。

代码解析:

举例:将"id"换成User::getId

其实是利用反射,使用函数User::getId找到对应的字段名,然后拼接到SQL中,从而避免了魔法值和硬编码。

8.2 重构6.4的代码

    @Test
    void testUpdateByLambdaQueryWrapper() {
        // 1. 要更新的数据
        User user = new User();
        user.setBalance(2000);

        // 2. 更新的条件
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
                .eq(User::getUsername, "jack");

        // 3. 执行更新
        userMapper.update(user, wrapper);
    }

8.3 重构7.2的代码

    @Test
    void testLambdaUpdateWrapper() {
        List<Long> idList = List.of(1L, 2L, 3L);
        LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<User>()
                .setSql(true, "balance = (balance - 200)")
                .in(User::getId, idList);

        userMapper.update(null, wrapper);
    }

1.9 总结

喜欢MybatisPlus(二)核心功能01 — 条件构造器这篇文章吗?您可以点击浏览我的博客主页 发现更多技术分享与生活趣事。

MybatisPlus(一)快速入门

作者陶其
2025年3月1日 19:11

感谢订阅陶其的个人博客!

视频课程地址:黑马商城项目

下一节:MybatisPlus(二)核心功能01 — 条件构造器
本节学习MybatisPlus(MP)的基本使用流程:

  1. 引入起步依赖
  2. 自定义Mapper继承BaseMapper
  3. 在实体类上添加注解声明 表信息
  4. 在application.yml中根据需要添加配置

MybatisPlus官网:https://baomidou.com

一、使用MybatisPlus流程

1.1 引入起步依赖

MyBatisPlus官方提供了starter,其中集成了Mybatis和MybatisPlus的所有功能,并且实现了自动装配效果。

因此我们可以用MybatisPlus的starter代替Mybatis的starter:

<!-- MybatisPlus -->
<dependency>
  <groupId>com.baomidou</groupId>
  <artifactId>mybatis-plus-boot-starter</artifactId>
  <version>3.5.3.1</version>
</dependency>

1.2 定义Mapper

自定义的Mapper继承MybatisPlus提供的BaseMapper接口:

public interface UserMapper extends BaseMapper<User> {

}

BaseMapper接口新增了很多CRUD的接口:

1.3 替换Mybatis案例

1.3.1 使用Mybatis的写法

1、UserMapper.java

package com.itheima.mp.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.mp.domain.po.User;
import org.apache.ibatis.annotations.Param;
import java.util.List;

public interface UserMapper extends BaseMapper<User> {

    void saveUser(User user);

    void deleteUser(Long id);

    void updateUser(User user);

    User queryUserById(@Param("id") Long id);

    List<User> queryUserByIds(@Param("ids") List<Long> ids);
}

2、UserMapper.xml

<?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.itheima.mp.mapper.UserMapper">
    <insert id="saveUser" parameterType="com.itheima.mp.domain.po.User">
        INSERT INTO `user` (`id`, `username`, `password`, `phone`, `info`, `balance`)
        VALUES
        (#{id}, #{username}, #{password}, #{phone}, #{info}, #{balance});
    </insert>
    <update id="updateUser" parameterType="com.itheima.mp.domain.po.User">
        UPDATE `user`
        <set>
            <if test="username != null">
                `username`=#{username}
            </if>
            <if test="password != null">
                `password`=#{password}
            </if>
            <if test="phone != null">
                `phone`=#{phone}
            </if>
            <if test="info != null">
                `info`=#{info}
            </if>
            <if test="status != null">
                `status`=#{status}
            </if>
            <if test="balance != null">
                `balance`=#{balance}
            </if>
        </set>
        WHERE `id`=#{id};
    </update>
    <delete id="deleteUser" parameterType="com.itheima.mp.domain.po.User">
        DELETE FROM user WHERE id = #{id}
    </delete>

    <select id="queryUserById" resultType="com.itheima.mp.domain.po.User">
        SELECT *
        FROM user
        WHERE id = #{id}
    </select>

    <select id="queryUserByIds" resultType="com.itheima.mp.domain.po.User">
        SELECT *
        FROM user
        <if test="ids != null">
            WHERE id IN
            <foreach collection="ids" open="(" close=")" item="id" separator=",">
                #{id}
            </foreach>
        </if>
        LIMIT 10
    </select>

</mapper>

3、测试

    @Test
    void testQueryByIds() {
        List<User> users = userMapper.queryUserByIds(List.of(1L, 2L, 3L, 4L));
        users.forEach(System.out::println);
    }

运行结果:

1.3.2 使用MybatisPlus的写法

1、UserMapper.java

package com.itheima.mp.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.mp.domain.po.User;

public interface UserMapper extends BaseMapper<User> {

}

2、UserMapper.xml

<?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.itheima.mp.mapper.UserMapper">

</mapper>

3、测试

    @Test
    void testQueryByIds() {
        List<User> users = userMapper.selectBatchIds(List.of(1L, 2L, 3L, 4L));
        users.forEach(System.out::println);
    }

运行结果:

运行结果相同。

二、常用注解

2.1 MyBatisPlus实现CRUD的原理

MyBatisPlus通过扫描实体类,并基于反射获取实体类信息作为数据库表信息。

命名原则(约定大于配置):

  • 类名驼峰转下划线作为表名
  • 名为id的字段作为主键
  • 变量名驼峰转下划线作为表的字段名

2.2 常见注解

MybatisPlus中比较常用的几个注解如下:

  • @TableName:用来指定表名
  • @TableId:用来指定表中的主键字段信息
  • @TableField:用来指定表中的普通字段信息

2.2.1 @TableName

用于给类指定表名。

用法:

@TableName("tb_user")
public class User {
}

2.2.2 @TableId

使用方法:

@TableId("id")
private Long userId;

或者:

@TableId(value="id", type=IdType.AUTO)
private Long userId;

IdType枚举:

  • AUTO:数据库自增长
  • INPUT:通过set方法自行输入
  • ASSIGN_ID:分配ID。(默认值)
    • 接口IdentifierGenerator的方法nextId来生成id,默认实现类为DefaultIdentifierGenerator雪花算法

2.2.3 @TableField

使用@TableField的常见场景:

  • 成员变量名与数据库字段名不一致

    • @TableField("username")
      private String name;
  • 成员变量名以is开头,且是布尔值。这种情况不处理,MP会自动去掉is

    • @TableField("is_married")
      private Boolean isMarried;
  • 成员变量名与数据库关键字冲突。这种情况不处理,执行SQL时数据库会报错

    • @TableField("`order`")
      private Integer order;
  • 成员变量不是数据库字段。这种情况不处理,MP会识别错误

    • @TableField(exist = false)
      private String address;

2.2.4 示例

如果实体(Java的类)字段名与表(MySQL)的列名不符合上面的命名原则,则使用这个注解进行辅助。

例如:

若数据表如下:

但是Java类却是如下:

public class User {

    private Long userId;

    private String name;

    private Boolean isMarried;

    private Integer order;

    private String address;
}

那么就需要使用这些注解进行辅助了:

@TableName("tb_user")
public class User {
    @TableId(value="id", type=IdType.AUTO)
    private Long userId;

    @TableField("username")
    private String name;

    @TableField("is_married")
    private Boolean isMarried;

    @TableField("`order`")
    private Integer order;

    @TableField(exist = false)
    private String address;
}

三、常用配置

MyBatisPlus的配置项继承了MyBatis原生配置和一些自己特有的配置。

例如:(用于application.yml或被其引用的yml中)

mybatis-plus:
  type-aliases-package: com.itheima.mp.domain.po # 别名扫描包
  mapper-locations: "classpath*:/mapper/**/*.xml" # Mapper.xml文件地址,默认值
  configuration:
    map-underscore-to-camel-case: true # 是否开启下划线和驼峰的映射
    cache-enabled: false # 是否开启二级缓存
  global-config: # (MP特有)全局配置
    db-config: # 数据库配置
      id-type: assign_id # id为雪花算法生成,还可以:auto
      update-strategy: not_null # 更新策略:只更新非空字段

具体可参考官方文档:使用配置 | MyBatis-Plus

喜欢MybatisPlus(一)快速入门这篇文章吗?您可以点击浏览我的博客主页 发现更多技术分享与生活趣事。

❌