MyBatisPlus详解(二)条件构造器Wrapper、自定义SQL、Service接口

文章目录

  • 前言
  • [2 核心功能](#2 核心功能)
    • [2.1 条件构造器](#2.1 条件构造器)
      • [2.1.1 Wrapper](#2.1.1 Wrapper)
      • [2.1.2 QueryWrapper](#2.1.2 QueryWrapper)
      • [2.1.3 UpdateWrapper](#2.1.3 UpdateWrapper)
      • [2.1.4 LambdaQueryWrapper](#2.1.4 LambdaQueryWrapper)
    • [2.2 自定义SQL](#2.2 自定义SQL)
      • [2.2.1 基本用法](#2.2.1 基本用法)
      • [2.2.2 多表关联](#2.2.2 多表关联)
    • [2.3 Service接口](#2.3 Service接口)
      • [2.3.1 IService](#2.3.1 IService)
        • [2.3.1.1 save](#2.3.1.1 save)
        • [2.3.1.2 remove](#2.3.1.2 remove)
        • [2.3.1.3 update](#2.3.1.3 update)
        • [2.3.1.4 get](#2.3.1.4 get)
        • [2.3.1.5 list](#2.3.1.5 list)
        • [2.3.1.6 count](#2.3.1.6 count)
        • [2.3.1.7 page](#2.3.1.7 page)
      • [2.3.2 基本用法](#2.3.2 基本用法)

前言

MyBatisPlus详解系列文章:

MyBatisPlus详解(一)项目搭建、@TableName、@TableId、@TableField注解与常见配置

2 核心功能

2.1 条件构造器

2.1.1 Wrapper

在BaseMapper接口提供的相关方法中,除了以id作为where条件,还支持更加复杂的where条件,即条件构造器Wrapper

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

Wrapper的子类AbstractWrapper提供了where中包含的所有条件构造方法:

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

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

2.1.2 QueryWrapper

使用QueryWrapper构建查询条件,例如:

  • select id, username, info, balance from t_user where id = 3 and username like '%o%' and balance >= 1000
java 复制代码
@Test
public void testQueryWrapper() {
    // select id, username, info, balance from t_user
    // where id = 3 and username like '%o%' and balance >= 1000
    QueryWrapper<User> wrapper = new QueryWrapper<User>()
            // 设置需要查询的字段
            .select("id", "username", "info", "balance")
            // id = 3
            .eq("id", 3)
            // like '%o%'
            .like("username", "o")
            // balance >= 1000
            .ge("balance", 1000);
    List<User> userList = userMapper.selectList(wrapper);
    userList.forEach(System.out::println);
}

执行以上单元测试结果如下:

复制代码
==>  Preparing: SELECT id,username,info,balance FROM t_user WHERE (id = ? AND username LIKE ? AND balance >= ?)
==> Parameters: 3(Integer), %o%(String), 1000(Integer)
<==      Total: 1
User(id=3, username=Hope, password=null, phone=null, info={"age": 25, "intro": "上进青年", "gender": "male"}, status=null, balance=20000, createTime=null, updateTime=null)
  • update t_user set balance = 2000 where username = 'Jack'
java 复制代码
@Test
void testUpdateByQueryWrapper() {
    // update t_user set balance = 2000 where username = 'Jack'
    QueryWrapper<User> wrapper = new QueryWrapper<User>()
            // username = 'Jack'
            .eq("username", "Jack");
    // 2.更新数据
    User user = new User();
    user.setBalance(2000);
    userMapper.update(user, wrapper);
}

执行以上单元测试结果如下:

复制代码
==>  Preparing: UPDATE t_user SET balance=? WHERE (username = ?)
==> Parameters: 2000(Integer), Jack(String)
<==    Updates: 0

2.1.3 UpdateWrapper

如上面的单元测试testUpdateByQueryWrapper()所示,update()方法更新时只能直接赋值,对于一些复杂的需求就难以实现,例如:

sql 复制代码
UPDATE t_user SET balance = balance - 200 WHERE id in (1, 2, 4);

SET语句的赋值结果是基于字段现有值的,这个时候就要利用UpdateWrapper中的setSql()方法了

java 复制代码
@Test
void testUpdateWrapper() {
    List<Long> ids = Arrays.asList(1L, 2L, 4L);
    // 1.生成SQL
    UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
            // SET balance = balance - 200
            .setSql("balance = balance - 200")
            // WHERE id in (1, 2, 4)
            .in("id", ids);
    // 2.更新
    // 注意第一个参数为null,则会基于UpdateWrapper中的setSQL来更新
    userMapper.update(null, wrapper);
}

执行以上单元测试结果如下:

复制代码
==>  Preparing: UPDATE t_user SET balance = balance - 200 WHERE (id IN (?,?,?))
==> Parameters: 1(Long), 2(Long), 4(Long)
<==    Updates: 2

2.1.4 LambdaQueryWrapper

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

而要不写字段名,又能知道字段名,其中一种办法是基于变量的gettter方法结合反射技术。为此,MybatisPlus又提供了一套基于Lambda的Wrapper,包含两个:LambdaQueryWrapper和LambdaUpdateWrapper。

java 复制代码
@Test
public void testLambdaQueryWrapper() {
    // select id, username, info, balance from t_user
    // where id = 3 and username like '%o%' and balance >= 1000
    LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
            // 设置需要查询的字段
            .select(User::getId, User::getUsername, User::getInfo, User::getBalance)
            // id = 3
            .eq(User::getId, 3)
            // username like '%o%'
            .like(User::getUsername, "o")
            // balance >= 1000
            .ge(User::getBalance, 1000);
    List<User> userList = userMapper.selectList(wrapper);
    userList.forEach(System.out::println);
}

执行该单元测试,可以得到和上面的testQueryWrapper()一样的结果,但这种写法不需要将字段名写死。未来如果需要修改字段名,也不需要这里的逻辑代码。

2.2 自定义SQL

在单元测试testUpdateWrapper(),编写了这样一行代码:.setSql("balance = balance - 200"),这种写法其实也是不好的,因为SQL语句最好都维护在持久层,而不是业务层

为此,MyBatis提供了自定义SQL功能,即利用Wrapper构建复杂的where条件,然后自己定义SQL语句剩余的部分。

也就是说,UPDATE t_user SET balance = balance - 200自己定义,WHERE id in (1, 2, 4)利用Wrapper构建。

2.2.1 基本用法

例如单元测试testUpdateWrapper()可以这样修改:

java 复制代码
@Test
public void testUpdateWrapper2() {
    List<Long> ids = Arrays.asList(1L, 2L, 4L);
    // 1.生成SQL
    UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
            // SET balance = balance - 200
            // .setSql("balance = balance - 200")
            // WHERE id in (1, 2, 4)
            .in("id", ids);
    // 2.更新
    // userMapper.update(null, wrapper);
    // 改为调用自定义的Mapper方法,直接传递Wrapper
    userMapper.deductBalanceByIds(200, wrapper);
}

然后在UserMapper中定义deductBalanceByIds()方法:

java 复制代码
// com.star.learning.mapper.UserMapper

@Update("UPDATE t_user SET balance = balance - #{money} ${uw.customSqlSegment}")
void deductBalanceByIds(@Param("money") int money, @Param("uw") UpdateWrapper<User> wrapper);

执行修改后的单元测试,执行结果和原来的是一样的。

2.2.2 多表关联

理论上来讲MyBatisPlus是不支持多表查询的,不过仍然可以利用Wrapper构建复杂where条件结合自定义SQL来实现多表查询的效果。

例如,要查询出所有收货地址在北京并且用户id在1、2、4之中的用户,其SQL语句为:

sql 复制代码
SELECT * FROM t_user u
INNER JOIN t_address a ON a.user_id = u.id
WHERE u.id IN (1,2,4)
AND a.city = '北京'

编写单元测试,利用Wrapper构建复杂where条件,并调用自定义的Mapper方法:

java 复制代码
@Test
public void testMultiTableQuery() {
    List<Long> ids = Arrays.asList(1L, 2L, 4L);
    // 1.利用Wrapper构建复杂where条件
    QueryWrapper<User> wrapper = new QueryWrapper<User>()
            .in("u.id", ids)
            .eq("a.city", "北京");
    // 2.调用自定义的Mapper方法
    User user = userMapper.queryUserByIdAndCity(wrapper);
    System.out.println(user);
}
java 复制代码
// com.star.learning.mapper.UserMapper

@Select("SELECT * FROM t_user u INNER JOIN t_address a ON a.user_id = u.id ${ew.customSqlSegment}")
User queryUserByIdAndCity(@Param("ew") QueryWrapper<User> wrapper);

执行以上单元测试,结果如下:

复制代码
==>  Preparing: SELECT * FROM t_user u INNER JOIN t_address a ON a.user_id = u.id WHERE (u.id IN (?,?,?) AND a.city = ?)
==> Parameters: 1(Long), 2(Long), 4(Long), 北京(String)
<==      Total: 1
User(id=2, username=Rose, password=123, phone=13900112223, info={"age": 19, "intro": "青涩少女", "gender": "female"}, status=1, balance=200, createTime=2024-04-21T10:13:35, updateTime=2024-04-21T16:10:20)

2.3 Service接口

MybatisPlus不仅提供了BaseMapper接口,还提供了通用的Service接口及默认实现ServiceImpl,封装了一些常用的service模板方法。

2.3.1 IService

2.3.1.1 save
  • save:插入一条记录
  • saveBatch:批量插入多条记录
  • saveOrUpdate:记录存在则更新记录,否则插入一条新记录
  • saveOrUpdateBatch:记录存在则批量修改,否则批量插入
2.3.1.2 remove
  • remove:根据条件删除
  • removeById:根据ID删除
  • removeByIds:根据ID批量删除
  • removeByMap:根据Map中的键值对为条件删除
  • removeBatchByIds:批量删除(jdbc批量提交)
2.3.1.3 update
  • update(Wrapper<T>):根据UpdateWrapper修改,Wrapper中包含set和where部分
  • update(T,Wrapper<T>):按照T内的数据修改与Wrapper匹配到的数据
  • updateById:根据id修改
  • updateBatchById:根据id批量修改
2.3.1.4 get
  • getById:根据id查询
  • getOne(Wrapper<T>):根据Wrapper查询1条记录
  • getBaseMapper:获取对应实体的BaseMapper
  • getMap(Wrapper<T>):根据Wrapper查询1条记录,封装为Map
2.3.1.5 list
  • list():查询所有
  • list(Wrapper<T>):根据Wrapper条件查询列表
  • listByIds():根据ID集合查询列表
2.3.1.6 count
  • count():查询总记录数
  • count(Wrapper<T>):根据Wrapper条件查询总记录数
2.3.1.7 page
  • page(IPage):无条件翻页查询
  • page(IPage, Wrapper<T>):根据Wrapper条件翻页查询

2.3.2 基本用法

创建一个IUserService接口,继承IService接口以拓展方法;同时,自定义UserServiceImpl实现类,继承ServiceImpl实现类,并实现IUserService接口。

java 复制代码
// com.star.learning.service.IUserService

public interface IUserService extends IService<User> {
}
java 复制代码
// com.star.learning.service.impl.UserServiceImpl

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

这样配置之后,导入IUserService接口,即可以使用IService接口中定义的各种方法。

接下来,实现下面这个接口:

接口 请求方式 请求路径 请求参数 返回值
新增用户 POST /user/add User

首先,导入相关依赖:

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

然后创建一个UserController类,并编写一个addUser()方法:

java 复制代码
// com.star.learning.controller.UserController

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private IUserService userService;

    @PostMapping("/add")
    public void addUser(@RequestBody User user) {
        boolean save = userService.save(user);
        System.out.println("新增用户结果 => " + save);
    }
}

最后进行功能测试,调用/user/add接口,查看控制台打印信息:

复制代码
==>  Preparing: INSERT INTO t_user ( username, password, phone, info, balance ) VALUES ( ?, ?, ?, ?, ? )
==> Parameters: Jim(String), 123456(String), 999(String), {"age": 20, "intro": "佛系青年", "gender": "male"}(String), 500(Integer)
<==    Updates: 1
新增用户结果 => true

接下来再来实现3个接口:

接口 请求方式 请求路径 请求参数 返回值
根据id查询用户 GET /user/{id} id User
根据id集合批量查询用户 GET /user/{id} id List<User>
删除用户 DELETE /user/{id} id

其代码如下:

java 复制代码
// com.star.learning.controller.UserController

@GetMapping("/{id}")
public User getById(@PathVariable("id") Long userId) {
    User user = userService.getById(userId);
    System.out.println("根据id查询用户 => " + user);
    return user;
}

@GetMapping
public List<User> queryUserByIds(@RequestParam("ids") List<Long> ids){
    List<User> users = userService.listByIds(ids);
    System.out.println("根据id集合查询用户列表 => " + users);
    return users;
}

@DeleteMapping("/{id}")
public void removeUserById(@PathVariable("id") Long userId){
    boolean remove = userService.removeById(userId);
    System.out.println("根据id删除用户 => " + remove);
}

调用这3个接口,查看控制台打印信息:

复制代码
// 根据id查询用户
==>  Preparing: SELECT id,username,password,phone,info,status,balance,create_time,update_time FROM t_user WHERE id=?
==> Parameters: 2(Long)
<==      Total: 1
根据id查询用户 => User(id=2, username=Rose, password=123, phone=13900112223, info={"age": 19, "intro": "青涩少女", "gender": "female"}, status=1, balance=200, createTime=2024-04-21T10:13:35, updateTime=2024-04-21T16:10:20)

// 根据id集合批量查询用户
==>  Preparing: SELECT id,username,password,phone,info,status,balance,create_time,update_time FROM t_user WHERE id IN ( ? , ? , ? )
==> Parameters: 1(Long), 2(Long), 3(Long)
<==      Total: 2
根据id集合查询用户列表 => [User(id=2, username=Rose, password=123, phone=13900112223, info={"age": 19, "intro": "青涩少女", "gender": "female"}, status=1, balance=200, createTime=2024-04-21T10:13:35, updateTime=2024-04-21T16:10:20), User(id=3, username=Hope, password=123, phone=13900112222, info={"age": 25, "intro": "上进青年", "gender": "male"}, status=1, balance=20000, createTime=2024-04-21T10:13:35, updateTime=2024-04-21T11:12:48)]

// 根据id删除用户
==>  Preparing: DELETE FROM t_user WHERE id=?
==> Parameters: 2(Long)
<==    Updates: 1
根据id删除用户 => true

可以看到,上述4个接口都直接在Controller类中即可实现,无需编写任何Service代码,非常方便。

不过,一些带有业务逻辑的接口则需要在Service中自定义实现了。例如下面这个接口:

接口 请求方式 请求路径 请求参数 返回值
根据id扣减用户余额 PUT {id}/deduction/{money} id,money
java 复制代码
// com.star.learning.controller.UserController

@PutMapping("{id}/deduction/{money}")
public void deductBalance(@PathVariable("id") Long id, @PathVariable("money")Integer money){
    System.out.println("扣减id=" + id + "的用户的余额" + money);
    userService.deductBalance(id, money);
}
java 复制代码
// com.star.learning.service.IUserService

public interface IUserService extends IService<User> {
    void deductBalance(Long userId, Integer money);
}
java 复制代码
// com.star.learning.service.impl.UserServiceImpl

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

    @Autowired
    private UserMapper userMapper;

    @Override
    public void deductBalance(Long userId, Integer money) {
        // 1.查询用户
        User user = getById(userId);
        System.out.println(user);
        // 2.判断用户状态
        if (user == null || user.getStatus() == 2) {
            throw new RuntimeException("用户状态异常");
        }
        // 3.判断用户余额
        if (user.getBalance() < money) {
            throw new RuntimeException("用户余额不足");
        }
        // 4.扣减余额
        // 2.2节自定义SQL创建了deductBalanceByIds方法可以直接使用
        UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
                .eq("id", userId);
        userMapper.deductBalanceByIds(money, wrapper);
    }
}

调用/user/3/deduction/200接口,查看控制台打印信息:

复制代码
扣减id=3的用户的余额200
==>  Preparing: SELECT id,username,password,phone,info,status,balance,create_time,update_time FROM t_user WHERE id=?
==> Parameters: 3(Long)
<==      Total: 1
User(id=3, username=Hope, password=123, phone=13900112222, info={"age": 25, "intro": "上进青年", "gender": "male"}, status=1, balance=20000, createTime=2024-04-21T10:13:35, updateTime=2024-04-21T11:12:48)
==>  Preparing: UPDATE t_user SET balance = balance - ? WHERE (id = ?)
==> Parameters: 200(Integer), 3(Long)
<==    Updates: 1

...

本节完,更多内容请查阅分类专栏:MyBatisPlus详解

感兴趣的读者还可以查阅我的另外几个专栏:

相关推荐
ZhengEnCi5 分钟前
@Parameter 注解技术解析-从 API 文档生成到接口描述清晰的 SpringBoot 利器
java·spring boot
数据库那些事儿5 分钟前
Qoder + ADB Supabase :5分钟GET超火AI手办生图APP
数据库·后端
IvorySQL6 分钟前
PostgreSQL 18 异步 I/O(AIO)调优指南
数据库·postgresql
用户68545375977697 分钟前
从"打电话"到"装修智能家居":让你的AI从话痨变成行动派!
后端
kakacc:10 分钟前
记录一次巧妙的SQL:一对多关联导致的 sum () 、count()等group函数重复计算问题
数据库·sql
AresXue13 分钟前
2025最新Java性能优化建议 应用 数据库 机器 网络
java
跟着珅聪学java23 分钟前
spring boot 整合 activiti 教程
android·java·spring
心随雨下29 分钟前
Redis中Geospatial 实际应用指南
数据库·redis·分布式·缓存
黑夜管理员32 分钟前
Sql Server安装报错“服务没有及时响应启动或控制请求”
数据库·sql server
Java水解1 小时前
Spring JDBC与KingbaseES深度集成:构建高性能国产数据库应用实战
后端·spring