Bootstrap

五天SpringCloud计划——DAY1之mybatis-plus的使用

一、引言

   咱也不知道为啥SpringCloud课程会先教mybatis-plus的使用,但是教都教了,就学了吧,学完之后觉得mybatis-plus中的一些方法还是很好用了,本文作为我学习mybatis-plus的总结提升,希望大家看完之后也可以熟悉mybatis-plus的使用

二、mybatis-plus

1.依赖引入

在xml文件中引入mybatis-plus依赖

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

注:引入这个依赖就代表吧mybatis的依赖随便引入引入进来了,可以删除你引入mybatis依赖

2.继承BaseMapper

之所以我们可以使用mybatis-plus中简单的单表CRUD方法,是因为mybatis-plus提供了相应的方法类BaseMapper,而我们的mapper只需要继承BaseMapper即可使用其中的方法

注:这里要指定泛型,如<User>这样才能知道你增删改查的对象是什么类型,User和数据库中的字段相对应

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

3.使用基本的CRUD方法

package com.itheima.mp.mapper;

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;
import java.util.List;

@SpringBootTest
class UserMapperTest {

    @Autowired
    private UserMapper userMapper;

    @Test
    void testInsert() {
        User user = new User();
        user.setId(5L);
        user.setUsername("Lucy");
        user.setPassword("123");
        user.setPhone("18688990011");
        user.setBalance(200);
        user.setInfo("{\"age\": 24, \"intro\": \"英文老师\", \"gender\": \"female\"}");
        user.setCreateTime(LocalDateTime.now());
        user.setUpdateTime(LocalDateTime.now());
        userMapper.insert(user);
    }

    @Test
    void testSelectById() {
        User user = userMapper.selectById(5L);
        System.out.println("user = " + user);
    }

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

    @Test
    void testUpdateById() {
        User user = new User();
        user.setId(5L);
        user.setBalance(20000);
        userMapper.updateById(user);
    }

    @Test
    void testDelete() {
        userMapper.deleteById(5L);
    }
}

代码中

1.userMapper.insert(user)          新增方法

2.userMapper.selectById(5L);     根据id单个查询全部信息

3.userMapper.selectBatchIds(List.of(1L, 2L, 3L, 4L, 5L));  根据id批量查询信息

4.userMapper.updateById(user);  根据id修改信息,这里默认指修改有值的部分,即balance

5.userMapper.deleteById(5L);  根据id删除信息

这些方法见名知意,也是很好记,如果仔细看测试输出,会发现其在底层是自动生成了相应的CRUD方法的,如下

11:05:01  INFO 15524 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
11:05:02  INFO 15524 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
11:05:02 DEBUG 15524 --- [           main] c.i.mp.mapper.UserMapper.selectById      : ==>  Preparing: SELECT id,username,password,phone,info,status,balance,create_time,update_time FROM user WHERE id=?
11:05:02 DEBUG 15524 --- [           main] c.i.mp.mapper.UserMapper.selectById      : ==> Parameters: 5(Long)
11:05:02 DEBUG 15524 --- [           main] c.i.mp.mapper.UserMapper.selectById      : <==      Total: 1
user = User(id=5, username=Lucy, password=123, phone=18688990011, info={"age": 21}, status=1, balance=20000, createTime=Fri Jun 30 11:02:30 CST 2023, updateTime=Fri Jun 30 11:02:30 CST 2023)

4.BaseMapper<User>中的<User>的讲究

看完上面的讲解,大家肯定会疑问,mybatis-plus是如何通过我指定的<User>泛型,找到我相应的数据库的表的,现在就给大家解开这个谜题

其实,在你的User实体类的定义时是有讲究的

比如我的User就是这样的

package com.itheima.mp.domain.po;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.itheima.mp.domain.vo.AddressVO;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.time.LocalDateTime;
import java.util.List;

@Data
@TableName("user")
public class User {
//@TableField使用场景
//  1.成员变量名与数据库名称不一致
//  2.成员变量以is开头而且是布尔值  isMarried
//  3.成员变量名与数据库关键字冲突  order

    /**
     * 用户id
     */
    @TableId(value = "id",type = IdType.AUTO)
    private Long id;

    /**
     * 用户名
     */
    @TableField("username")
    private String username;

    /**
     * 密码
     */
    private String password;

    /**
     * 注册手机号
     */
    private String phone;

    /**
     * 详细信息
     */
    private String info;

    /**
     * 使用状态(1正常 2冻结)
     */
    private Integer status;

    /**
     * 账户余额
     */
    private Integer balance;

    /**
     * 创建时间
     */
    private LocalDateTime createTime;

    /**
     * 更新时间
     */
    private LocalDateTime updateTime;
    @ApiModelProperty("用户的收货地址")
    private List<AddressVO> address;
}

默认情况下

  • MybatisPlus会把PO实体的类名驼峰转下划线作为表名

  • MybatisPlus会把PO实体的所有变量名驼峰转下划线作为表的字段名,并根据变量类型推断字段类型

  • MybatisPlus会把名为id的字段作为主键

注:名词解释PO,指实体类

可以发现里面有很多的注解,也就是这些注解在起作用,下面给大家讲解一下这些注解

1.@TableName("user"),指定实体类名对应的表名,也就是User实体类就对应user表

2.@TableId(value = "id",type = IdType.AUTO),指定主键对应的字段,type中的type = IdType.AUTO,代表主键自增

还有其他的type,这里说明一下

AUTO

数据库 ID 自增

NONE

无状态,该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)

INPUT

insert 前自行 set 主键值

ASSIGN_ID

分配 ID(主键类型为 Number(Long 和 Integer)或 String)(since 3.3.0),使用接口IdentifierGenerator的方法nextId(默认实现类为DefaultIdentifierGenerator雪花算法)

ASSIGN_UUID

分配 UUID,主键类型为 String(since 3.3.0),使用接口IdentifierGenerator的方法nextUUID(默认 default 方法)

3.@TableField("username")指定对象属性对应的表中字段,用到它有三种情况

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

  • 成员变量是以isXXX命名,按照JavaBean的规范,MybatisPlus识别字段时会把is去除,这就导致与数据库不符。

  • 成员变量名与数据库一致,但是与数据库的关键字冲突。使用@TableField注解给字段名添加转义字符:``

5.更加复杂的数据库操作

这里指更加复杂的where条件,因为前文都是简单的根据id怎么样,根据id列表怎么样,但是如果指定其他字段查询又该怎么办呢,这里就是为了解决这个问题

在解决这个问题之前,给大家介绍几个条件构造的抽象类,就是这些抽象类里面可以装我们的条件,然后就可以用这些条件去作为where条件

接下来给大家介绍一下其中常用的几个抽象类

1)QueryWrapper

下面是一个使用QueryWrapper的例子,用来查询出名字中带o的,存款大于等于1000元的人。

@Test
void testQueryWrapper() {
    // 1.构建查询条件 where name like "%o%" AND balance >= 1000
    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便是一个条件构造器,其中构造了三个条件:

1..select("id", "username", "info", "balance")

表示要查找id、username、info、balance四个字段

2..like("username", "o")

表示模糊查询,即username中带o

3..ge("balance", 1000)

表示balance的值大于1000

之后调用userMapper.selectList(wrapper);查询结果列表,将我们的条件构造器wrapper传入进去

让我们再举一个例子

更新用户名为jack的用户的余额为2000,代码如下:

@Test
void testUpdateByQueryWrapper() {
    // 1.构建查询条件 where name = "Jack"
    QueryWrapper<User> wrapper = new QueryWrapper<User>().eq("username", "Jack");
    // 2.更新数据,user中非null字段都会作为set语句
    User user = new User();
    user.setBalance(2000);
    userMapper.update(user, wrapper);
}

这里包含了两部分,怎么改,改哪个:

怎么改---

就像前文讲到的updateById(user),创建一个对象,mybatis-plus默认修改其中有值的字段

改哪里---

这时候就得请出我们的QueryWrapper了,构造条件username为Jack

2)UpdateWrapper

这里也给大家举例

更新id为1,2,4的用户的余额,扣200

@Test
void testUpdateWrapper() {
    List<Long> ids = List.of(1L, 2L, 4L);
    // 1.生成SQL
    UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
            .setSql("balance = balance - 200") // SET balance = balance - 200
            .in("id", ids); // WHERE id in (1, 2, 4)
        // 2.更新,注意第一个参数可以给null,也就是不填更新字段和数据,
    // 而是基于UpdateWrapper中的setSQL来更新
    userMapper.update(null, wrapper);
}

大家可以看到这个需求,它需要自定义SQL语句,即"balance = balance - 200",这句需要加在sql语句中set的后面,UpdateWrapper便提供了这种功能

3)LambdaQueryWrapper

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

于是出现了LambdaQueryWrapper和LambdaUpdateWrapper,他们可以通过反射获取属性名,并不需要写死

代码如下

@Test
void testLambdaQueryWrapper() {
    // 1.构建条件 WHERE username LIKE "%o%" AND balance >= 1000
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.lambda()
            .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);
}

这里的User::getId便代替了id

4)自定义SQL

在企业中,通常SQL语句维护在持久层,所以我们写在业务层的方法是不被允许的,所以我们来改造一下我们的代码

源代码---

@Test
void testUpdateWrapper() {
    List<Long> ids = List.of(1L, 2L, 4L);
    // 1.生成SQL
    UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
            .setSql("balance = balance - 200") // SET balance = balance - 200
            .in("id", ids); // WHERE id in (1, 2, 4)
        // 2.更新,注意第一个参数可以给null,也就是不填更新字段和数据,
    // 而是基于UpdateWrapper中的setSQL来更新
    userMapper.update(null, wrapper);
}

改造之后的代码---

@Test
void testCustomWrapper() {
    // 1.准备自定义查询条件
    List<Long> ids = List.of(1L, 2L, 4L);
    QueryWrapper<User> wrapper = new QueryWrapper<User>().in("id", ids);

    // 2.调用mapper的自定义方法,直接传递Wrapper
    userMapper.deductBalanceByIds(200, wrapper);
}
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 org.apache.ibatis.annotations.Update;
import org.apache.ibatis.annotations.Param;

public interface UserMapper extends BaseMapper<User> {
    @Select("UPDATE user SET balance = balance - #{money} ${ew.customSqlSegment}")
    void deductBalanceByIds(@Param("money") int money, @Param("ew") QueryWrapper<User> wrapper);
}

这里需要注意三点

1.在int money前加@Param("money")是为了确保money接收的是传来的money

2.在QueryWrapper<User> wrapper)前要加@Param("ew"),这样才可以识别到是一个条件构造器

3.${ew.customSqlSegment},这是字符串拼接,写法是固定的

这里我其实有一个疑问,如果SQL语句维护在持久层,那么UpdateWrapper的setSql是否还有用的必要,或者说UpdateWrapper是否还有用的必要

5)多表关联查询

理论上来讲MyBatisPlus是不支持多表查询的,不过我们可以利用Wrapper中自定义条件结合自定义SQL来实现多表查询的效果。

例如,我们要查询出所有收货地址在北京的并且用户id在1、2、4之中的用户

我们可以这样实现

查询条件这样来构建:

@Test
void testCustomJoinWrapper() {
    // 1.准备自定义查询条件
    QueryWrapper<User> wrapper = new QueryWrapper<User>()
            .in("u.id", List.of(1L, 2L, 4L))
            .eq("a.city", "北京");

    // 2.调用mapper的自定义方法
    List<User> users = userMapper.queryUserByWrapper(wrapper);

    users.forEach(System.out::println);
}

然后在UserMapper中自定义方法:

@Select("SELECT u.* FROM user u INNER JOIN address a ON u.id = a.user_id ${ew.customSqlSegment}")
List<User> queryUserByWrapper(@Param("ew")QueryWrapper<User> wrapper);
6)小总结

这部分内容比较多,我总结一下要点

mybatis-plus处理“更复杂的数据库操作”主要是通过条件构造器来实现的,个人觉得最好用的是LambdaQueryWrapper,因为其比较全面,包括反射,条件构造,在更新的方法也可以用到它

而条件构造器主要是构造where之后的内容,比如我们所讲到的自定义sql,字符串拼接的也是where部分的内容

6.Service接口

MybatisPlus不仅提供了BaseMapper,还提供了通用的Service接口及默认实现,封装了一些常用的service模板方法。 通用接口为IService,默认实现为ServiceImpl,其中封装的方法可以分为以下几类:

  • save:新增

  • remove:删除

  • update:更新

  • get:查询单个结果

  • list:查询集合结果

  • count:计数

  • page:分页查询

其实学到这里的时候我就在想,BaseMapper已经够全面了,为啥要来一个IService,我这里猜测一下,是为了迎合三层架构(瞎猜的)

这里的学习方法就和学习BaseMapper很像了

1)继承

首先,定义IUserService,继承IService

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> {
    // 拓展自定义方法
}

 然后,编写UserServiceImpl类,继承ServiceImpl,实现UserService

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.domain.po.service.IUserService;
import com.itheima.mp.mapper.UserMapper;
import org.springframework.stereotype.Service;

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

这样就可以使用mybatis-plus准备好的方法了

2)CRUD

又来到了我们的增删改查环节,实现下面四个接口噢

编号

接口

请求方式

请求路径

请求参数

返回值

1

新增用户

POST

/users

用户表单实体

2

删除用户

DELETE

/users/{id}

用户id

3

根据id查询用户

GET

/users/{id}

用户id

用户VO

4

根据id批量查询

GET

/users

用户id集合

用户VO集合

package com.itheima.mp.controller;

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 lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

import java.util.List;

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

    private final IUserService userService;

    @PostMapping
    @ApiOperation("新增用户")
    public void saveUser(@RequestBody UserFormDTO userFormDTO){
        // 1.转换DTO为PO
        User user = BeanUtil.copyProperties(userFormDTO, User.class);
        // 2.新增
        userService.save(user);
    }

    @DeleteMapping("/{id}")
    @ApiOperation("删除用户")
    public void removeUserById(@PathVariable("id") Long userId){
        userService.removeById(userId);
    }

    @GetMapping("/{id}")
    @ApiOperation("根据id查询用户")
    public UserVO queryUserById(@PathVariable("id") Long userId){
        // 1.查询用户
        User user = userService.getById(userId);
        // 2.处理vo
        return BeanUtil.copyProperties(user, UserVO.class);
    }

    @GetMapping
    @ApiOperation("根据id集合查询用户")
    public List<UserVO> queryUserByIds(@RequestParam("ids") List<Long> ids){
        // 1.查询用户
        List<User> users = userService.listByIds(ids);
        // 2.处理vo
        return BeanUtil.copyToList(users, UserVO.class);
    }
}

这里的CRUD方法和BaseMapper的用法相似,只是名称不一致

1.userService.save(user)                    新增用户

2.userService.removeById(userId)      根据id删除用户

3.userService.getById(userId)              根据id查询用户

4.userService.listByIds(ids)                   根据id列表查询用户

这里有需要注意的方法

1.BeanUtil.copyProperties(userFormDTO, User.class)  字段映射

2.BeanUtil.copyToList(users, UserVO.class)  批量字段映射

3.@RequiredArgsConstructor+private final IUserService userService;

   这里注入userService,是一种更推荐的写法

3)controller+service+mapper

下面尝试实现根据id扣减用户余额

我们发现在UserController中好像实现不了这个功能,因为牵扯到业务逻辑的代码我们要放在service层去实现,不然代码就太乱了,而且牵扯到了自定义sql,还要用到mapper层

业务逻辑如下

  • 判断用户状态是否正常

  • 判断用户余额是否充足

 1.UserController,这里不需要处理业务逻辑,所以直接调用service

@PutMapping("{id}/deduction/{money}")
@ApiOperation("扣减用户余额")
public void deductBalance(@PathVariable("id") Long id, @PathVariable("money")Integer money){
    userService.deductBalance(id, money);
}

 2.这里是接口哈

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> {
    void deductBalance(Long id, Integer money);
}

3.实现类 

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 {
    @Override
    public void deductBalance(Long id, Integer money) {
        // 1.查询用户
        User user = getById(id);
        // 2.判断用户状态
        if (user == null || user.getStatus() == 2) {
            throw new RuntimeException("用户状态异常");
        }
        // 3.判断用户余额
        if (user.getBalance() < money) {
            throw new RuntimeException("用户余额不足");
        }
        // 4.扣减余额
        baseMapper.deductMoneyById(id, money);
    }
}

 4.mapper,这里是自定义sql拼接的地方

@Update("UPDATE user SET balance = balance - #{money} WHERE id = #{id}")
void deductMoneyById(@Param("id") Long id, @Param("money") Integer money);
4)Lambda

实现一个根据复杂条件查询用户的接口,查询条件如下:

  • name:用户名关键字,可以为空

  • status:用户状态,可以为空

  • minBalance:最小余额,可以为空

  • maxBalance:最大余额,可以为空

对于像这种的复杂条件查询,我们最好先定义一个查询条件实体,UserQuery实体:

package com.itheima.mp.domain.query;

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

我们有两种方法去实现这个功能

1.LambdaQueryWrapper

@GetMapping("/list")
@ApiOperation("根据id集合查询用户")
public List<UserVO> queryUsers(UserQuery query){
    // 1.组织条件
    String username = query.getName();
    Integer status = query.getStatus();
    Integer minBalance = query.getMinBalance();
    Integer maxBalance = query.getMaxBalance();
    LambdaQueryWrapper<User> wrapper = new QueryWrapper<User>().lambda()
            .like(username != null, User::getUsername, username)
            .eq(status != null, User::getStatus, status)
            .ge(minBalance != null, User::getBalance, minBalance)
            .le(maxBalance != null, User::getBalance, maxBalance);
    // 2.查询用户
    List<User> users = userService.list(wrapper);
    // 3.处理vo
    return BeanUtil.copyToList(users, UserVO.class);
}

2.lambdaQuery

@GetMapping("/list")
@ApiOperation("根据id集合查询用户")
public List<UserVO> queryUsers(UserQuery query){
    // 1.组织条件
    String username = query.getName();
    Integer status = query.getStatus();
    Integer minBalance = query.getMinBalance();
    Integer maxBalance = query.getMaxBalance();
    // 2.查询用户
    List<User> users = userService.lambdaQuery()
            .like(username != null, User::getUsername, username)
            .eq(status != null, User::getStatus, status)
            .ge(minBalance != null, User::getBalance, minBalance)
            .le(maxBalance != null, User::getBalance, maxBalance)
            .list();
    // 3.处理vo
    return BeanUtil.copyToList(users, UserVO.class);
}

我只能说,这个简单的有些过分了,大家可以自己看一下

7.代码生成

这部分学到了一个代码生成器的使用

我是不怎么喜欢用这个,大家想用的话可以用用

8.静态工具

有的时候Service之间也会相互调用,为了避免出现循环依赖问题,MybatisPlus提供一个静态工具类:Db,其中的一些静态方法与IService中方法签名基本一致,也可以帮助我们实现CRUD功能:

那么问题来了,我们为什么要用它,这里我给出一个解释,因为懒,不想多次注入多个service,同时循环依赖

那我们来用一下吧,改造根据id用户查询的接口,查询用户的同时返回用户收货地址列表

1.controller

@GetMapping("/{id}")
@ApiOperation("根据id查询用户")
public UserVO queryUserById(@PathVariable("id") Long userId){
    // 基于自定义service方法查询
    return userService.queryUserAndAddressById(userId);
}

2.service 

package com.itheima.mp.service;

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

public interface IUserService extends IService<User> {
    void deduct(Long id, Integer money);

    UserVO queryUserAndAddressById(Long userId);
}
@Override
public UserVO queryUserAndAddressById(Long userId) {
    // 1.查询用户
    User user = getById(userId);
    if (user == null) {
        return null;
    }
    // 2.查询收货地址
    List<Address> addresses = Db.lambdaQuery(Address.class)
            .eq(Address::getUserId, userId)
            .list();
    // 3.处理vo
    UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
    userVO.setAddresses(BeanUtil.copyToList(addresses, AddressVO.class));
    return userVO;
}

其实很多时候什么代码放在controller什么代码放在service都是主观感觉,我就得就是三部分代码能完成的就不用service,只需要定义,数据库,返回三部分,每一部分一句代码,当然是个人感觉

这里有要注意的一点

使用Db的方法时,需要多加一个Address.class,得让他知道你对应的实体类是谁

9.逻辑删除

先给大家说一下什么是逻辑删除以及如何实现逻辑删除

为什么要逻辑删除

比如你在淘宝的消费记录你删除了,那么你就看不到了,但是这个数据还得给商家看等等,所以不能真正的删除,他只是你看不到了

如何逻辑删除

对应其实现,只是在你的数据库中加了一个is_deleted字段,不删除为0,删除就为1

对于mybatis-plus很简单,只需要在application.yml中配置逻辑删除字段

mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: deleted # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

之后执行删除操作时,会修改底层代码为逻辑删除

注:只有MybatisPlus生成的SQL语句才支持自动的逻辑删除,自定义SQL需要自己手动处理逻辑删除。

10.通用枚举

这里给大家将一下为何要使用枚举

比如用户的账号状态,有正常冻结两种,但是我们为了代码的可维护性,要在数据库存0,1,但是你又希望在数据的传输和查看过程中,一直是正常和冻结,这时就要使用枚举了

1.首先,我们要定义一个枚举类

package com.itheima.mp.enums;

import com.baomidou.mybatisplus.annotation.EnumValue;
import lombok.Getter;

@Getter
public enum UserStatus {
    NORMAL(1, "正常"),
    FREEZE(2, "冻结")
    ;
    private final int value;
    private final String desc;

    UserStatus(int value, String desc) {
        this.value = value;
        this.desc = desc;
    }
}

2.然后把User类中的status字段改为UserStatus 类型

3.在application.yaml文件中添加配置

mybatis-plus:
  configuration:
    default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler

4.两个注解

MybatisPlus提供了@EnumValue注解来标记枚举属性

不传输json格式时会传输枚举的名字

在UserStatus枚举中通过@JsonValue注解标记JSON序列化时展示的字段

传输json类型时会传输枚举的值

11.JSON类型处理器

如果我们有个数据库的字段里面存储的时json类型的字符串时就会出现一种问题

需要在写入数据库时手动转为String,再读取数据库时,手动转换为对象,这会非常麻烦。

所以mybatis-plus提供了一种非常方便的工具JacksonTypeHandler处理器

下面教大家如何去使用它

比如现在有一个字段info,它存储的内容是这样的

package com.itheima.mp.domain.po;

import lombok.Data;

@Data
public class UserInfo {
    private Integer age;
    private String intro;
    private String gender;
}

那么,我们需要在它对应的实体类里面加入如下注解

这样便可以自动执行数据的转换了

12.配置加密

用不到,嘿嘿,不写了

13.分页插件

这里直接展示成品

1.首先配置一下分页插件

@Configuration
public class MybatisConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//      1.创建分页插件
        PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
//      2.分页查询上限
        paginationInnerInterceptor.setMaxLimit(1000L);
        interceptor.addInnerInterceptor(paginationInnerInterceptor);
        return interceptor;
    }
}

2.整几个分页需要的类

1)PageDTO<T>

package com.itheima.mp.domain.dto;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.itheima.mp.domain.po.User;
import com.itheima.mp.domain.vo.UserVO;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.util.Collections;
import java.util.List;

@Data
@ApiModel("分页结果")
public class PageDTO<T> {
    @ApiModelProperty("总条数")
    private Integer total;
    @ApiModelProperty("总页数")
    private Integer pages;
    @ApiModelProperty("结果")
    private List<T> list;
    public static <PO,VO> PageDTO<VO> of(Page<PO> p,Class<VO> clazz){
        PageDTO<VO> dto = new PageDTO<>();
        dto.setTotal((int) p.getTotal());
        dto.setPages((int) p.getPages());
        List<PO> records=p.getRecords();
        if(CollUtil.isEmpty(records)){
            dto.setList(Collections.emptyList());
            return dto;
        }
        dto.setList(BeanUtil.copyToList(records,clazz));
        return dto;
    }

}

2)PageQuery 

package com.itheima.mp.domain.query;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.itheima.mp.domain.po.User;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

@Data
@ApiModel(description = "分页查询条件实体")
public class PageQuery {
    @ApiModelProperty("页码")
    private Integer pageNo=1;
    @ApiModelProperty("每页数量")
    private Integer pageSize=5;
    @ApiModelProperty("排序字段")
    private String orderBy;
    @ApiModelProperty("是否升序")
    private Boolean isAsc=true;
    public <T> Page<T> toMpPage(OrderItem ... items) {
        Page<T> page=Page.of(pageNo,pageSize);
        if(StrUtil.isNotBlank(orderBy)) {
            page.addOrder(new OrderItem(orderBy, isAsc));
        }else if(items!=null){
            page.addOrder(items);
        }
        return page;
    }
    public <T> Page<T> toMpPage(String defaultSortBy ,Boolean defaultAsc) {
        return toMpPage(new OrderItem(defaultSortBy,defaultAsc));
    }
    public <T> Page<T> toMpPageDefaultSortByCreateTime(OrderItem ... items) {
        return toMpPage(new OrderItem("create_time",false));
    }
    public <T> Page<T> toMpPageDefaultSortByUpdateTime(OrderItem ... items) {
        return toMpPage(new OrderItem("update_time",false));
    }

}

3)直接使用

   @Override
    public PageDTO<UserVO> queryUsersPage(UserQuery query) {
        String name=query.getName();
        Integer status=query.getStatus();
//        1.构建查询条件
        Page<User> page =query.toMpPageDefaultSortByUpdateTime();
//        2.分页查询
        Page<User> p=lambdaQuery()
                .like(name!=null,User::getUsername,name)
                .eq(status!=null,User::getStatus,status)
                .page(page);
//        3.封装vo结果

        return PageDTO.of(p,UserVO.class);
    }

三、结语

丸辣,怎么天就黑了了,本来还打算吧Docker学了的

能学完就把博客发出来吧,我尽量

不知道下一篇文章是

五天SpringCloud计划——DAY1之Docker的使用

还是

五天SpringCloud计划——DAY2之Docker的使用

;