MyBatisPlus完整技术汇总二(完结)

业务层接口

  • MybatisPlus不仅提供了BaseMapper接口,还提供了通用的Service接口及默认实现,封装了一些常用的service模板方法。 通用接口为IService,默认实现类为ServiceImpl,其中封装的方法可以分为以下8类:
    • save:新增
    • remove:删除
    • update:更新
    • get:查询单个结果
    • list:查询集合结果
    • count:计数
    • page:分页查询
    • lambda

快速入门

本项目名为MbPlusServiceDemo,已上传至Gitee,可自行下载

此处只进行不同步骤演示,具体搭建步骤可详见SpringBoot完整技术汇总

  • Step1: 创建一个SpringBoot整合MyBatisPlus的项目MbPlusServiceDemo

    注意:SpringBoot整合MyBatisPlus的项目与创建SpringBoot整合MyBatis的项目过程相同,区别就是:

    ​ 整合MyBatisPlus项目只用添加一个MySQL Driver起步依赖即可,如图所示

    ​ 因为后期在pom.xml文件中导入的MyBatisPlus坐标中包含了MyBatis的坐标

    在这里插入图片描述

  • Step2: 创建数据库mp并在该库中创建表user 并使IDEA与数据库建立连接 ,SQL代码如下

    CREATE TABLE `user` (
    	`id` BIGINT(19) NOT NULL AUTO_INCREMENT COMMENT '用户id',
    	`username` VARCHAR(50) NOT NULL COMMENT '用户名' COLLATE 'utf8_general_ci',
    	`password` VARCHAR(128) NOT NULL COMMENT '密码' COLLATE 'utf8_general_ci',
    	`phone` VARCHAR(20) NULL DEFAULT NULL COMMENT '注册手机号' COLLATE 'utf8_general_ci',
    	`info` JSON NOT NULL COMMENT '详细信息',
    	`status` INT(10) NULL DEFAULT '1' COMMENT '使用状态(1正常 2冻结)',
    	`balance` INT(10) NULL DEFAULT NULL COMMENT '账户余额',
    	`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    	`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    	PRIMARY KEY (`id`) USING BTREE,
    	UNIQUE INDEX `username` (`username`) USING BTREE
    )
    COMMENT='用户表'
    COLLATE='utf8_general_ci'
    ENGINE=InnoDB
    ROW_FORMAT=COMPACT
    AUTO_INCREMENT=5
    ;
    
    -- 正在导出表  mp.user 的数据:~4 rows (大约)
    
    INSERT INTO `user` (`id`, `username`, `password`, `phone`, `info`, `status`, `balance`, `create_time`, `update_time`) VALUES
    	(1, 'Jack', '123', '13900112224', '{"age": 20, "intro": "佛系青年", "gender": "male"}', 1, 1600, '2023-05-19 20:50:21', '2023-06-19 20:50:21'),
    	(2, 'Rose', '123', '13900112223', '{"age": 19, "intro": "青涩少女", "gender": "female"}', 1, 600, '2023-05-19 21:00:23', '2023-06-19 21:00:23'),
    	(3, 'Hope', '123', '13900112222', '{"age": 25, "intro": "上进青年", "gender": "male"}', 1, 100000, '2023-06-19 22:37:44', '2023-06-19 22:37:44'),
    	(4, 'Thomas', '123', '17701265258', '{"age": 29, "intro": "伏地魔", "gender": "male"}', 1, 800, '2023-06-19 23:44:45', '2023-06-19 23:44:45');
    

    在这里插入图片描述

  • Step3: 在pom.xml文件中添加数据源坐标依赖(博主以Druid为例)、MyBatisPlus的坐标依赖、lombok坐标依赖

    注意:若不添加,则会使用SpringBoot的默认数据源。此处根据实际情况来决定是否添加数据源。博主使用druid数据源

    此处只给出相关的两个数据源坐标,可自行选择使用

    <!--lombok坐标-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    <!--druid坐标-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.2.18</version>
    </dependency>
    <!--c3p0坐标
    <dependency>
        <groupId>com.mchange</groupId>
        <artifactId>c3p0</artifactId>
        <version>0.9.5.5</version>
    </dependency>
    -->
    <!--MyBatisPlus坐标-->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
        <version>3.5.9</version>
    </dependency>
    
  • Step4: 将配置文件application.properties改为application.yml格式的配置文件,并在该配置文件中配置数据库连接信息、数据源信息以及MyBatisPlus的运行日志

    注意:

    ​ 配置MyBatisPlus的运行日志在此处写出来只是为了演示,后续可根据实际情况进行配置,在该快速入门中博主是进行该配置的。

    spring:
      # 配置数据库连接信息以及数据源信息
      datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/mp?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
        username: root
        password: 123456
    
    mybatis-plus:
      configuration:
        # 配置MyBatisPlus的运行日志
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
      # 实体类的别名扫描包---即设置别名
      type-aliases-package: at.guigu.domain.po
    
  • Step5: 创建一个与三层架构包同级的domain包,并在该包下创建po包,然后在该po包下实体类User,代码如下

    package at.guigu.domain.po;
    
    import lombok.Data;
    
    import java.time.LocalDateTime;
    
    @Data
    public class User {
        // 用户id
        private Long id;
        // 用户名
        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;
    }
    
  • Step6: 创建持久层dao包,并在该包下创建继承BaseMapper<T>接口的UserDao接口,且泛型为对应的实体类的名称

    注意:此时持久层代码就已完全实现,因为MyBatisPlus底层已经封装了一系列的增删改查语句,后续只需调用就可以了

    package at.guigu.dao;
    
    import at.guigu.domain.po.User;
    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import org.apache.ibatis.annotations.Mapper;
    
    @Mapper
    public interface UserDao extends BaseMapper<User> {
    }
    
  • Step7: 创建业务层service包

    • 在该包下创建继承IService<T>接口的IUserService接口

      package at.guigu.service;
      import at.guigu.domain.po.User;
      import com.baomidou.mybatisplus.extension.service.IService;
      
      public interface IUserService extends IService<User> {
      }
      
    • 在该包下创建子包impl,并在子包下创建继承ServiceImpl<M extends BaseMapper<T>, T>类、实现IUserService接口的类UserServiceImpl,同时用@Service注解标注该类

      ServiceImpl<M extends BaseMapper<T>, T>泛型中的第一个参数为自己定义的持久层接口,第二个参数为对应的实体类

      package at.guigu.service.impl;
      
      import at.guigu.dao.UserDao;
      import at.guigu.domain.po.User;
      import at.guigu.service.IUserService;
      import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
      import org.springframework.stereotype.Service;
      
      @Service
      public class UserServiceImpl extends ServiceImpl<UserDao, User> implements IUserService {
      }
      

需求示例

需求:基于RestFul风格实现CURD

  • Step1: 在测试目录test下创建测试包at.guigu.service.impl,并在impl包下创建测试类UserServiceImpl,初始代码如下:

    创建测试类时可以利用Alt+Enter快速在测试目录下创建对应业务层实现类的测试类

    package at.guigu.service.impl;
    
    import at.guigu.domain.po.User;
    import at.guigu.service.IUserService;
    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 UserServiceImplTest {
        @Autowired
        private IUserService userService;
        
    }
    

示例1: 新增用户

package at.guigu.service.impl;

import at.guigu.domain.po.User;
import at.guigu.service.IUserService;
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 UserServiceImplTest {
    @Autowired
    private IUserService userService;

    //新增用户
    @Test
    void test1() {
        //模拟前端传来的数据
        User user = new User();
        user.setUsername("LiLei");
        user.setPassword("123456");
        user.setPhone("17736789882");
        user.setBalance(200);
        user.setInfo("{\"age\": 20, \"intro\": \"优秀小子\", \"gender\": \"female\"}");
        user.setCreateTime(LocalDateTime.now());
        user.setUpdateTime(LocalDateTime.now());
        // 新增用户
        userService.saveOrUpdate(user);
    }
}

在这里插入图片描述

示例2: 根据id删除用户

package at.guigu.service.impl;

import at.guigu.domain.po.User;
import at.guigu.service.IUserService;
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 UserServiceImplTest {
    @Autowired
    private IUserService userService;

    // 根据id删除用户
    @Test
    void test2() {
        List ids = List.of(1873307622315921410L);
        userService.removeByIds(ids);
    }
}

在这里插入图片描述

示例3: 根据id查询用户

package at.guigu.service.impl;

import at.guigu.domain.po.User;
import at.guigu.service.IUserService;
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 UserServiceImplTest {
    @Autowired
    private IUserService userService;

    // 根据用户id查询用户
    @Test
    void test3() {
        Long id = 1L;
        User user = userService.getById(id);
        System.out.println(user);
    }
}

在这里插入图片描述

示例4: 根据用户id集合查询用户

package at.guigu.service.impl;

import at.guigu.domain.po.User;
import at.guigu.service.IUserService;
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 UserServiceImplTest {
    @Autowired
    private IUserService userService;
    
    // 根据用户id集合查询用户
    @Test
    void test4() {
        List ids = List.of(1L, 2L, 3L);
        List users = userService.listByIds(ids);
        users.forEach(System.out::println);
    }
}

在这里插入图片描述

示例5:查询名字中带a的用户

package at.guigu.service.impl;

import at.guigu.domain.po.User;
import at.guigu.service.IUserService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
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 UserServiceImplTest {
    @Autowired
    private IUserService userService;

    // 查询名字中带a的用户
    @Test
    void test5() {
        List<User> users = userService.list(new LambdaQueryWrapper<User>()
                .like(User::getUsername, "a"));
        users.forEach(System.out::println);
    }
}

在这里插入图片描述

批量数据处理

  • 有三种方式
    • 方式一:普通for循环(不推荐)
    • 方式二:IService批量处理
    • 方式三:开启rewriteBatchedStatements参数
    • 以上三种方式中,不推荐使用方式一,性能太垃圾,方式二和方式三联合使用时性能最高

需求:批量插入十万条用户数据

package at.guigu.service.impl;

import at.guigu.domain.po.User;
import at.guigu.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

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

@SpringBootTest
public class UserServiceImplTestTwo {
    @Autowired
    private IUserService userService;
    
    private User InsertUser(int i) {
        User user = new User();
        user.setUsername("user_" + 1);
        user.setPassword("123456");
        user.setPhone("17736789882");
        user.setBalance(200);
        user.setInfo("{\"age\": 20, \"intro\": \"优秀小子\", \"gender\": \"female\"}");
        user.setCreateTime(LocalDateTime.now());
        user.setUpdateTime(LocalDateTime.now());
        return user;
    }

    // 批量插入十万条用户数据
    void test() {
        List<User> userList = new ArrayList<User>(1000);
        for (int i = 0; i < 10000; i++) {
            userList.add(InsertUser(i));
            if (i % 1000 == 0) {
                userService.saveBatch(userList);
                userList.clear();
            }
        }
    }
}
  • MyBatisPlus的批量数据处理效率很高,原因是

    • MyBatisPlus的批量数据处理是基于PrepareStatement的预编译模式,然后批量提交。

      • 但是这种方法仍有提升空间,因为它在数据库执行时还是会有多条insert语句,然后逐条插入数据,如下所示:

        Preparing: INSERT INTO user ( username, password, phone, info, balance, create_time, update_time ) VALUES ( ?, ?, ?, ?, ?, ?, ? )
        Parameters: user_1, 123, 18688190001, "", 2000, 2023-12-01, 2023-12-01
        
        Preparing: INSERT INTO user ( username, password, phone, info, balance, create_time, update_time ) VALUES ( ?, ?, ?, ?, ?, ?, ? )
        Parameters: user_2, 123, 18688190002, "", 2000, 2023-12-01, 2023-12-01
        ...
        
    • 因此为使批量数据处理效率更高,则在配置文件application.yml中,在数据源配置的url的属性值最后添加参数&rewriteBatchedStatements=true ,此时配置文件代码如下:

      spring:
        # 配置数据库连接信息以及数据源信息
        datasource:
          type: com.alibaba.druid.pool.DruidDataSource
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://localhost:3306/mp?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
          username: root
          password: 123456
      
      mybatis-plus:
        configuration:
          # 配置MyBatisPlus的运行日志
          log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
        # 实体类的别名扫描包---即设置别名
        type-aliases-package: at.guigu.domain.po
      

      此时就会将多条SL语句合并为一条,如下所示:

      INSERT INTO user ( username, password, phone, info, balance, create_time, update_time )
      VALUES 
      (user_1, 123, 18688190001, "", 2000, 2023-12-01, 2023-12-01),
      (user_2, 123, 18688190002, "", 2000, 2023-12-01, 2023-12-01),
      (user_3, 123, 18688190003, "", 2000, 2023-12-01, 2023-12-01),
      (user_4, 123, 18688190004, "", 2000, 2023-12-01, 2023-12-01);
      

业务层的相关接口

IService<T>接口

IService接口相关方法说明

  • 新增

    在这里插入图片描述

    • save是新增单个元素
    • saveBatch是批量新增
    • saveOrUpdate是根据id判断,如果数据存在就更新,不存在则新增
    • saveOrUpdateBatch是批量的新增或修改
  • 删除

    在这里插入图片描述

    • removeById:根据id删除

    • removeByIds:根据id批量删除

    • removeByMap:根据Map中的键值对为条件删除

    • remove(Wrapper):根据Wrapper条件删除

  • 修改

    在这里插入图片描述

    • updateById:根据id修改
    • update(Wrapper):根据UpdateWrapper修改,Wrapper中包含setwhere部分
    • update(T,Wrapper):按照T内的数据修改与Wrapper匹配到的数据
    • updateBatchById:根据id批量修改
  • Get

    在这里插入图片描述

    • getById:根据id查询1条数据
    • getOne(Wrapper):根据Wrapper查询1条数据
    • getBaseMapper:获取Service内的BaseMapper实现,某些时候需要直接调用Mapper内的自定义SQL时可以用这个方法获取到Mapper
  • List

    在这里插入图片描述

    • listByIds:根据id批量查询
    • list(Wrapper):根据Wrapper条件查询多条数据
    • list():查询所有
  • Count

    在这里插入图片描述

    • count():统计所有数量
    • count(Wrapper):统计符合Wrapper条件的数据数量
  • getBaseMapper

    当我们在service中要调用Mapper中自定义SQL时,就必须获取service对应的Mapper,就可以通过这个方法:

    在这里插入图片描述

IRepository<T>接口

  • IRepository<T>接口是IService<T>接口的父接口,里面定义了所有的增删改查的方法

  • IRepository<T>接口中的几个特殊方法

    方法解释
    default QueryChainWrapper<T> query()相当于QueryWrapper
    default LambdaQueryChainWrapper<T> lambdaQuery()相当于LambdaQueryWrapper
    default LambdaQueryChainWrapper<T> lambdaQuery(T entity)
    default UpdateChainWrapper<T> update()相当于LambdaQueryWrapper
    default LambdaUpdateChainWrapper<T> lambdaUpdate()相当于LambdaQueryWrapper

lambdaQuery()方法的需求示例

示例1:查询名字为Rose的用户(假设名为Rose的只有一个)

/**
 * `lambdaQuery()`方法的需求示例
 */
@SpringBootTest
class UserServiceImplTestThree {
    @Autowired
    private IUserService userService;

    // 查询名字为Rose的用户(假设名为Rose的只有一个)
    @Test
    void test1() {
        User user = userService.lambdaQuery()
                .eq(User::getUsername, "Rose")
                .one();// one()代表结果只有一个
        System.out.println(user);
    }
}

示例2:查询名字中含a的用户

/**
 * `lambdaQuery()`方法的需求示例
 */
@SpringBootTest
class UserServiceImplTestThree {
    @Autowired
    private IUserService userService;

    // 查询名字中含a的用户
    @Test
    void test2() {
        List<User> users = userService.lambdaQuery()
                .like(User::getUsername, "a")
                .list();// list()代表结果有多个
        users.forEach(System.out::println);
    }
}

示例3:统计名字中含o的用户数量

/**
 * `lambdaQuery()`方法的需求示例
 */
@SpringBootTest
class UserServiceImplTestThree {
    @Autowired
    private IUserService userService;
    // 统计名字中含o的用户数量
    @Test
    void test3() {
        Long nums = userService.lambdaQuery()
                .eq(User::getUsername, "o")
                .count();
        System.out.println("名字中含o的用户数量为:" + nums);
    }
}

示例4:定义一个方法,接收参数为username、status、minBalance、maxBalance,参数可以为空,此时:

​ 若username不为空,则采用模糊查询

​ 若status不为空,则采用精确匹配

​ 若minBalance不为空,则余额必须大于minBalance

​ 若maxBalance不为空,则余额必须小于maxBalance

/**
 * `lambdaQuery()`方法的需求示例
 */
@SpringBootTest
class UserServiceImplTestThree {
    @Autowired
    private IUserService userService;
    /**
     * 定义一个方法,接收参数为username、status、minBalance、maxBalance,参数可以为空,此时:
     *若username不为空,则采用模糊查询
     *若status不为空,则采用精确匹配
     *若minBalance不为空,则余额必须大于minBalance
     *若maxBalance不为空,则余额必须小于maxBalance
     * @param username
     * @param status
     * @param min
     * @param max
     * @return List<User>
     */
    public List<User> test4(String username, Integer status, Long min, Long max) {
        return userService.lambdaQuery()
                .like(username != null, User::getUsername, username)
                .eq(status != null, User::getStatus, status)
                .gt(min != null, User::getBalance, min)
                .lt(max != null, User::getBalance, max)
                .list();
    }
}

lambdaUpdate()方法的需求示例

基于IService的lambdaUpdate()方法实现一个更新方法,满足以下需求:

​ 参数为id、username、balance

​ id或username至少一个不为空,根据id或username精确匹配用户

​ 将匹配到的用户的余额修改为balance

​ 若balance为0,则将用户的status修改为冻结状态2

package at.guigu.service.impl;

import at.guigu.domain.po.User;
import at.guigu.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

/**
 * `lambdaUpdate()`方法的需求示例
 */
@SpringBootTest
class UserServiceImplTestFour {
    @Autowired
    private IUserService userService;

    /**
     * 基于IService的`lambdaUpdate()`方法实现一个更新方法,满足以下需求:
     *  参数为id、username、balance
     *  id或username至少一个不为空,根据id或username精确匹配用户
     *  将匹配到的用户的余额修改为balance
     *  若balance为0,则将用户的status修改为冻结状态2
     * @param username
     * @param id
     * @param username
     * @param balance
     * @return List<User>
     */
    void test1(Long id, String username, Integer balance) {
        if (id == null && username == null) {
            throw new RuntimeException("id或username至少一个不为空");
        }
        userService.lambdaUpdate()
                .set(User::getBalance, balance)
                .set(balance == 0, User::getStatus, 2)
                .eq(id != null, User::getId, id)
                .eq(username != null, User::getUsername, username)
                .update(); // update()是用来执行更新方法,若不加该方法就无法更新
    }
}

静态工具

  • 背景引入

    • 假设现在业务层有两个接口,且实现类分别为A和B。此时若A需要使用B,且B也要使用A(即Service之间进行相互调用),就需要用@Autowired注解进行依赖注入,这样就造成了循环依赖的问题
    • 虽然Spring可以解决这样简单的循环依赖,但是对于多层嵌套的复杂循环依赖来说,Spring还是解决不了
    • 这就会导致问题出现,因此就有两种解决方法
      • 方法一:A用@Autowired注解依赖注入B对应的持久层接口的Bean;B用@Autowired注解依赖注入A对应的持久层接口的Bean
      • 方法二:使用静态工具(本小节重点)
  • MyBatisPlus提供了一个静态工具类Db,可供我们解决复杂依赖注入问题。该类中的方法均为静态方法

    • 新增相关静态方法

      在这里插入图片描述

      在这里插入图片描述

    • 删除相关静态方法

      在这里插入图片描述

    • 查询相关静态方法

      在这里插入图片描述

      在这里插入图片描述

    • count

      在这里插入图片描述

    • lambda

      在这里插入图片描述

    • page

      在这里插入图片描述

  • 该静态工具类Db与IService接口的用法一模一样,区别就是:

    • 静态工具类中的有些方法要传入要操作的表所对应的po实体类(这就是解决循环依赖的关键)
    • 使用该静态工具类时,不需要使用@Autowired进行依赖注入,因为它传入了要操作的表所对应的po实体类
  • 注意

    • 一般在业务内部若不需要使用另一个持久层对应的接口,则尽量使用IService中的方法
    • 若在业务内部需要使用另一个持久层对应的接口,此时为了避免循环依赖,则使用BaseMapper或静态工具类Db

拓展功能

本项目MpTuoDemo已上传至Gitee,可自行下载

代码自动生成

  • 背景引入

    • 在一个项目中,持久层、业务层、po层等基础代码是固定不变的(如图所示),如果均交给人工就太浪费时间,所以就有了代码自动生成来简化开发

      在这里插入图片描述

  • 作用:主要用于 自动生成实体类、Mapper 接口、XML 配置文件、Service 层及其实现类 等,减少了大量的重复性编码工作,提升了开发效率

  • Step1: 新建一个空白项目MpTuoDemo,然后在idea中安装插件MyBatisPlus

  • Step2: 按图示操作连接数据库

    在这里插入图片描述

  • Step3: 按图示操作生成对应的包以及代码

    在这里插入图片描述

    完成后如下图所示

    在这里插入图片描述

枚举处理器

  • 背景引入
    • 在实体类中一般都存在状态字段,且在实际项目中状态字段的值很多,不可能记得过来,这就会导致效率低下,出错率高。因此我们就可以通过定义一个枚举类来解决该问题
  • @EnumValue:指定枚举值在数据库中存储的实际值
    • 即枚举类的其它属性被标记为 @EnumValue 时,处理器会自动识别这个属性,然后
      • 在添加数据时,MyBatisPlus会自动将枚举类型所对应的该属性的值写到数据库表的对应字段中
      • 在查询数据时,MyBatisPlus会自动根据数据库表的对应的字段的值来返回对应的枚举类型

快速入门

本示例以User实体类中的状态字段status为例

在这里插入图片描述

  • Step1: 创建一个与三层架构包同级的enums枚举包,并在该包下创建利用enum关键字声明的枚举类UserStatus,代码如下

    枚举类的创建可详见Javase基础部分内容

    • Step1-1: 定义枚举类本类主要属性(属性之间用逗号隔开,最后用分号结束)

    • Step1-2: 定义枚举类其它普通属性vlauedesc

      • 其中vlaue需要加上@EnumValue注解
    • Step1-3: 枚举类中普通属性通过构造器赋值

    • Step1-4: 给枚举类添加@AllArgsConstructor@Getter注解

      • @AllArgsConstructor:自动生成一个包含类中所有字段的构造函数

        注意:在枚举类中不包含枚举类本类主要属性,只包括其它普通属性。若不加该注解则必须通过私有的有参构造器来给其它普通属性赋值。

        博主此处演示了未加该注解的方法

    package at.guigu.enums;
    
    import com.baomidou.mybatisplus.annotation.EnumValue;
    import lombok.Getter;
    
    @Getter
    public enum UserStatus {
        // 定义枚举类本类主要属性
        FROZEN(0, "禁止使用"),
        NORMAL(1, "已激活"),
        ;
        // 定义枚举类其它普通属性
        @EnumValue
        private int value;
        private String desc;
    
        // 枚举类中普通属性通过构造器赋值(由于未加@AllArgsConstructor,所以必须利用有参构造器给其它属性赋值)
        private UserStatus(Integer value, String desc) {
            this.value = value;
            this.desc = desc;
        }
    }
    
  • Step2: 更改实体类User中属性status的数据类型为枚举类

    在这里插入图片描述

  • Step3: 在配置文件中配置默认的枚举类型处理器 (default-enum-type-handler),完整配置文件application.yml代码如下

    spring:
      # 配置数据库连接信息以及数据源信息
      datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/mp?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
        username: root
        password: 123456
    
    mybatis-plus:
      configuration:
        # 配置MyBatisPlus的运行日志
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
        # 配置默认的枚举类型处理器
        default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
      # 实体类的别名扫描包---即设置别名
      type-aliases-package: at.guigu.domain.po
    
  • Step4: 代码测试—新增用户

    /**
     * 测试枚举处理器
     */
    @SpringBootTest
    class UserServiceImplTest {
        @Autowired
        private IUserService userService;
    
        // 新增用户
        @Test
        void test1() {
            //模拟前端传来的数据
            User user = new User();
            user.setUsername("LiLei");
            user.setPassword("123456");
            user.setPhone("17736789882");
            user.setBalance(200);
            user.setInfo("{\"age\": 20, \"intro\": \"优秀小子\", \"gender\": \"female\"}");
            user.setCreateTime(LocalDateTime.now());
            user.setUpdateTime(LocalDateTime.now());
    
            user.setStatus(UserStatus.NORMAL);
            // 新增用户
            userService.saveOrUpdate(user);
        }
    }
    

    在这里插入图片描述

  • Step5: 代码测试—根据用户id查询用户

    /**
     * 测试枚举处理器
     */
    @SpringBootTest
    class UserServiceImplTest {
        @Autowired
        private IUserService userService;
    
        // 根据用户id查询用户
        @Test
        void test2() {
            Long id = 1L;
            User user = userService.getById(id);
            System.out.println(user);
        }
    }
    

    在这里插入图片描述

注意

  • 若不给枚举类UserStatus中的对应的属性添加@EnumValue注解,则此时UserStatus代码如下:

    • Step1: 让枚举类UserStatus实现IEnum<T>接口,且泛型为Integer

    • Step2: 定义枚举类本类主要属性(属性之间用逗号隔开,最后用分号结束)

    • Step3: 定义枚举类其它普通属性vlauedesc

    • Step4: 枚举类中普通属性通过构造器赋值

      • Step4-1: 给枚举类添加@AllArgsConstructor@Getter注解

        • @AllArgsConstructor:自动生成一个包含类中所有字段的构造函数

          注意:在枚举类中不包含枚举类本类主要属性,只包括其它普通属性。若不加该注解则必须通过私有的有参构造器来给其它普通属性赋值。

          博主此处演示了未加该注解的方法

    • Step5: 重写IEnum接口中的getter方法

      • 该方法返回数据库表的字段所对应的那个其它普通属性(相当于代替了@EnumValue注解)
    package at.guigu.enums;
    
    import com.baomidou.mybatisplus.annotation.IEnum;
    import lombok.Getter;
    
    @Getter
    public enum UserStatus implements IEnum<Integer> {
        // 定义枚举类本类主要属性
        FROZEN(0, "禁止使用"),
        NORMAL(1, "已激活"),
        ;
        // 定义枚举类其它普通属性
        private int value;
        private String desc;
    
        // 枚举类中普通属性通过构造器赋值
        private UserStatus(Integer value, String desc) {
            this.value = value;
            this.desc = desc;
        }
        // 重写IEnum接口中的getter方法
        @Override
        public Integer getValue() {
            return this.value;
        }
    }
    
  • 在快速入门的运行截图中可看出status返回的是枚举实例的名称即status=NORMAL,在实际项目开发中,可能要求返回的是枚举实例名称的详细说明所对应的普通属性的属性值,即status=已激活,此时代码更改如下:

    • 枚举类UserStatus未实现IEnum<T>接口: 给对枚举实例进行解释说明的普通属性添加@JsonValue

      package at.guigu.enums;
      
      import com.baomidou.mybatisplus.annotation.EnumValue;
      import lombok.Getter;
      
      @Getter
      public enum UserStatus {
          // 定义枚举类本类主要属性
          FROZEN(0, "禁止使用"),
          NORMAL(1, "已激活"),
          ;
          // 定义枚举类其它普通属性
          @EnumValue
          private int value;
          @JsonValue
          private String desc;
      
          // 枚举类中普通属性通过构造器赋值(由于未加@AllArgsConstructor,所以必须利用有参构造器给其它属性赋值)
          private UserStatus(Integer value, String desc) {
              this.value = value;
              this.desc = desc;
          }
      }
      
    • 枚举类UserStatus实现IEnum<T>接口: 重写toString方法

      package at.guigu.enums;
      
      import com.baomidou.mybatisplus.annotation.IEnum;
      import lombok.Getter;
      
      @Getter
      public enum UserStatus implements IEnum<Integer> {
          // 定义枚举类本类主要属性
          FROZEN(0, "禁止使用"),
          NORMAL(1, "已激活"),
          ;
          // 定义枚举类其它普通属性
          private int value;
          private String desc;
      
          // 枚举类中普通属性通过构造器赋值
          private UserStatus(Integer value, String desc) {
              this.value = value;
              this.desc = desc;
          }
          // 重写IEnum接口中的getter方法
          @Override
          public Integer getValue() {
              return this.value;
          }
          // 重写toString方法
          @Override
          public String toString() {
              return this.desc;  // 返回 desc 属性值
          }
      }
      

JSON处理器

  • 背景引入

    • 在数据库的user表中有一个info字段为JSON类型,格式为:

      {"age": 20, "intro": "佛系青年", "gender": "male"}
      
      {
          "age": 20, 
          "intro": "佛系青年", 
          "gender": "male"
      }
      
    • 但是在其对应的实体类中是String类型,如图所示

      在这里插入图片描述

    • 由于类型不一样,所以在读取info的属性值时就很不方便。如果要方便获取,info的类型最好是一个Map或者实体类。而一旦我们把info改为对象类型,就需要在写入数据库时手动转为String,再读取数据库时,手动转换为对象,这会非常麻烦。

    • 因此MybatisPlus提供了很多特殊类型字段的类型处理器,解决特殊字段类型与数据库类型转换的问题。例如处理JSON就可以使用JacksonTypeHandler处理器。

快速入门

  • Step1: 在pojo包下创建UserInfo类,代码如下:

    • @NoArgsConstructor:自动生成一个无参构造函数
    • @AllArgsConstructor(staticName = "fo"):自动生成一个包含类中所有字段的构造函数,并且利用staticName属性来为该有参构造器设置一个静态方法名,以供方便后续调用
    package at.guigu.domain.po;
    
    import lombok.Data;
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor(staticName = "fo")
    public class UserInfo {
        //年龄
        private Integer age;
        //简介
        private String intro;
        //性别
        private String gender;
    }
    
  • Step2: 将实体类User中的info属性的类型由String改为UserInfo

    在这里插入图片描述

  • Step3: 继续更改User实体类代码

    • 给实体类中info属性添加@TableField注解,并给该注解添加JSON的类型处理器,代码如下:
    • 给实体类的@TableName注解添加autoResultMap属性,且值设为true
      • autoResultMap:是否自动构建 resultMap 并使用,默认为false
      • 若不加该属性,则前端无法获取info值
    package at.guigu.domain.po;
    
    import at.guigu.enums.UserStatus;
    import com.baomidou.mybatisplus.annotation.TableField;
    import com.baomidou.mybatisplus.annotation.TableName;
    import com.baomidou.mybatisplus.annotation.IdType;
    import com.baomidou.mybatisplus.annotation.TableId;
    import java.time.LocalDateTime;
    import java.io.Serializable;
    
    import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
    import lombok.Data;
    import lombok.EqualsAndHashCode;
    import lombok.experimental.Accessors;
    
    @Data
    @EqualsAndHashCode(callSuper = false)
    @Accessors(chain = true)
    @TableName(value = "user", autoResultMap = true)
    public class User implements Serializable {
    
        private static final long serialVersionUID = 1L;
        
        // 用户id
        @TableId(value = "id", type = IdType.AUTO)
        private Long id;
        // 用户名
        private String username;
        // 密码
        private String password;
        // 注册手机号
        private String phone;
        // 详细信息
        // 利用@TableField注解的typeHandler属性配置JSON的类型处理器
        @TableField(typeHandler = JacksonTypeHandler.class)
        private UserInfo info;
        // 使用状态(1正常 2冻结)
        private UserStatus status;
        // 账户余额
        private Integer balance;
    	// 创建时间
        private LocalDateTime createTime;
        // 更新时间
        private LocalDateTime updateTime;
    
    }
    

    1

  • Step4: 代码测试—新增用户

    /**
     * 测试枚举处理器
     */
    @SpringBootTest
    class UserServiceImplTest {
        @Autowired
        private IUserService userService;
    
        //新增用户
        @Test
        void test1() {
            //模拟前端传来的数据
            User user = new User();
            user.setUsername("LiMing");
            user.setPassword("123456");
            user.setPhone("17736789882");
            user.setBalance(200);
            user.setCreateTime(LocalDateTime.now());
            user.setUpdateTime(LocalDateTime.now());
    
            user.setStatus(UserStatus.FROZEN);
            
            user.setInfo(UserInfo.fo(16, "Java老师", "female"));
            // 新增用户
            userService.saveOrUpdate(user);
        }
    }
    
  • Step5: 代码测试—根据用户id查询用户

    /**
     * 测试枚举处理器
     */
    @SpringBootTest
    class UserServiceImplTest {
        @Autowired
        private IUserService userService;
    
        // 根据用户id查询用户
        @Test
        void test2() {
            Long id = 1L;
            User user = userService.getById(id);
            System.out.println(user);
        }
    }
    

配置加密

在实际项目中,配置文件中的用户名、密码、url等是不会直接暴露出来的,而是以一种加密的方式显示,让使用人员无法得知。

  • MyBatisPlus提供了一个基于AES算法的加密工具来对配置中的敏感信息进行加密处理

    • MyBatis-Plus 允许你使用加密后的字符串来配置数据库连接信息。在 YML 配置文件中,以 mpw: 开头的配置项将被视为加密内容,如下所示

      spring:
        datasource:
          url: mpw:qRhvCwF4GOqjessEB3G+a5okP+uXXr96wcucn2Pev6Bf1oEMZ1gVpPPhdDmjQqoM
          password: mpw:Hzy5iliJbwDHhjLs1L0j6w==
          username: mpw:Xb+EgsyuYRXw7U7sBJjBpA==
      
  • Step1: 在项目MpTuoDemo自带的测试类MpTuoDemoApplicationTests中编写代码

    package at.guigu;
    
    import com.baomidou.mybatisplus.core.toolkit.AES;
    import org.junit.jupiter.api.Test;
    import org.springframework.boot.test.context.SpringBootTest;
    
    @SpringBootTest
    class MpTuoDemoApplicationTests {
    
        @Test
        void contextLoads() {
            // 生成16位随机AES密钥
            String randomKey = AES.generateRandomKey();
            System.out.println("randomKey:" + randomKey);
            // 利用密码对用户名加密
            String username = AES.encrypt("root", randomKey);
            System.out.println("username:" + username);
            // 利用密钥对密码进行加密
            String password = AES.encrypt("123456", randomKey);
            System.out.println("password:" + password);
        }
    
    }
    

    运行该测试类即可得出对应的密钥和密文

    在这里插入图片描述

  • Step2: 将用户名、密码的密文分别复制到配置文件的对应位置处

    注意:格式为:mpw:密文

    在这里插入图片描述

配置解密

  • 若项目打包成jar包运行,则命令为:java -jar 项目jar包名.jar --mpw.key=生成的随机16位密钥

  • 若在idea的测试包test下单元测试时,则必须给@SpringBootTest注解添加属性args,且属性值为"--mpw.key=生成的随机16位密钥"

    在这里插入图片描述

  • 若用idea的引导类启动该项目时,步骤如下:

    Program arguments属性值为:--mpw.key=生成的随机16位密钥

    在这里插入图片描述

插件功能

  • MyBatisPlus基于MyBatis的Interceptor接口实现了一个基础拦截器,并在内部保存了MyBatisPlus的内置拦截器的集合

  • MyBatisPlus提供了6个内置拦截器

    内置拦截器解释
    PaginationInnerInterceptor自动分页
    OptimisticLockerInnerInterceptor乐观锁
    DynamicTableNameInnerInterceptor动态表名
    BlockAttackInnerInterceptor防止全表更新与删除
    IllegalSQLInnerInterceptorSQL 性能规范
    TenantLineInnerInterceptor多租户

分页插件

  • Step1: 在pom.xml文件中添加mybatis-plus-jsqlparser坐标依赖

    博主使用的MyBatisPlus为3.5.9的版本,于 v3.5.9 起,PaginationInnerInterceptor 已分离出来。如需使用,则需单独引入 mybatis-plus-jsqlparser 依赖;若使用3.5.9之前的版本则不需要导入以下依赖

    <!--mybatis-plus-jsqlparser-->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-jsqlparser</artifactId>
        <version>3.5.9</version>
    </dependency>
    
  • Step2: 创建一个与三层架构包同级的config包并在该包下创建一个MyBatisPlus的配置类MpConfig,代码如下

    • Step2-1: 在该类中创建一个使用@Bean注解的拦截器方法mybatisPlusInterceptor()
    • Step2-2: 在该方法中定义MyBatisPlus的拦截器并添加具体的拦截器—分页拦截器

    注意:DbType 枚举类包括多个数据库类型,用于指定具体的数据库类型。当有多个数据库则可以不配具体类型,否则都建议配上具体的DbType

    在实际项目中,若要在MyBatis的配置类中添加多个插件方法(即定义多个bean),则分页对应的bean必须最后添加

    package at.guigu.config;
    
    import com.baomidou.mybatisplus.annotation.DbType;
    import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
    import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    @MapperScan("at.guigu.mapper")
    public class MpConfig {
    
        /**
         * 添加拦截器方法
         * @return
         */
        @Bean
        public MybatisPlusInterceptor mybatisPlusInterceptor() {
            // 定义MyBatisPlus的拦截器
            MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();
            // 配置分页拦截器---注意:若有多个数据源则可以不配具体类型,否则都建议配上具体的DbType(即数据库)
            mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
            return mpInterceptor;
        }
    }
    
  • Step3: 创建测试类PageTest,代码如下

    package at.guigu.service.impl;
    
    import at.guigu.domain.po.User;
    import at.guigu.mapper.UserMapper;
    import at.guigu.service.IUserService;
    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.baomidou.mybatisplus.core.metadata.IPage;
    import com.baomidou.mybatisplus.core.metadata.OrderItem;
    import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    
    @SpringBootTest(args = "--mpw.key=hqWQFW3P0eTiPgbt")
    public class PageTest {
        @Autowired
        private IUserService userService;
        
        @Test
        public void test1() {
            // 构建分页条件
            IPage<User> p = new Page<>(1, 2);
    
            // 根据指定参数进行升序排列
            LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
            wrapper.orderByAsc(User::getBalance);
            
            userService.page(p, wrapper);
    
            /*等同于
            userService.lambdaQuery()
                    .orderByAsc(User::getBalance)
                    .page(p);
            */
    
            System.out.println("当前页码值为:" + p.getCurrent());
            System.out.println("每页显示的数据数量为:" + p.getSize());
            System.out.println("总页数为" + p.getPages());
            System.out.println("总数据数量为" + p.getTotal());
            System.out.println("当前页的数据集合为:" + p.getRecords());
        }
        
        @Test
        public void test2() {
            // 构建分页条件
            Page<User> p = Page.of(1, 2);
            // 根据指定参数进行升序排列
            page.addOrder(OrderItem.ascs("balance"));
    
            Page<User> p = userService.page(p, null);//等同于 userService.page(p, null);
            //注意:IUservice接口的page方法返回的p就是构建分页条件中的p
            
            System.out.println("当前页码值为:" + p.getCurrent());
            System.out.println("每页显示的数据数量为:" + p.getSize());
            System.out.println("总页数为" + p.getPages());
            System.out.println("总数据数量为" + p.getTotal());
            System.out.println("当前页的数据集合为:" + p.getRecords());
        }
    }
    
  • 测试类中写了两种分页方法

    • IPage<T>接口中无排序的相关方法,必须使用Wrapper抽象类的子类来封装条件语句
    • IPage<T>接口的实现类Page<T>中存在排序相关方法,则直接使用即可

案例

本项目MpDemo已上传至Gitee,可自行下载

需求:编写一个UserController接口,实现User的分页查询,要求如下:

在这里插入图片描述

  • 返回结果—返回的为JSON对象

    在这里插入图片描述

环境准备一

  • Step1: 创建一个SpringBoot整合MyBatisPlus的项目MbPlusServiceDemo

    注意:SpringBoot整合MyBatisPlus的项目与创建SpringBoot整合MyBatis的项目过程相同,区别就是:

    ​ 整合MyBatisPlus项目只用添加一个MySQL Driver起步依赖即可,如图所示

    ​ 因为后期在pom.xml文件中导入的MyBatisPlus坐标中包含了MyBatis的坐标

    在这里插入图片描述

  • Step2: 创建数据库mp并在该库中创建表user 并使IDEA与数据库建立连接 ,SQL代码如下

    CREATE TABLE `user` (
    	`id` BIGINT(19) NOT NULL AUTO_INCREMENT COMMENT '用户id',
    	`username` VARCHAR(50) NOT NULL COMMENT '用户名' COLLATE 'utf8_general_ci',
    	`password` VARCHAR(128) NOT NULL COMMENT '密码' COLLATE 'utf8_general_ci',
    	`phone` VARCHAR(20) NULL DEFAULT NULL COMMENT '注册手机号' COLLATE 'utf8_general_ci',
    	`info` JSON NOT NULL COMMENT '详细信息',
    	`status` INT(10) NULL DEFAULT '1' COMMENT '使用状态(1正常 2冻结)',
    	`balance` INT(10) NULL DEFAULT NULL COMMENT '账户余额',
    	`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    	`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    	PRIMARY KEY (`id`) USING BTREE,
    	UNIQUE INDEX `username` (`username`) USING BTREE
    )
    COMMENT='用户表'
    COLLATE='utf8_general_ci'
    ENGINE=InnoDB
    ROW_FORMAT=COMPACT
    AUTO_INCREMENT=5
    ;
    
    -- 正在导出表  mp.user 的数据:~4 rows (大约)
    
    INSERT INTO `user` (`id`, `username`, `password`, `phone`, `info`, `status`, `balance`, `create_time`, `update_time`) VALUES
    	(1, 'Jack', '123', '13900112224', '{"age": 20, "intro": "佛系青年", "gender": "male"}', 1, 1600, '2023-05-19 20:50:21', '2023-06-19 20:50:21'),
    	(2, 'Rose', '123', '13900112223', '{"age": 19, "intro": "青涩少女", "gender": "female"}', 1, 600, '2023-05-19 21:00:23', '2023-06-19 21:00:23'),
    	(3, 'Hope', '123', '13900112222', '{"age": 25, "intro": "上进青年", "gender": "male"}', 1, 100000, '2023-06-19 22:37:44', '2023-06-19 22:37:44'),
    	(4, 'Thomas', '123', '17701265258', '{"age": 29, "intro": "伏地魔", "gender": "male"}', 1, 800, '2023-06-19 23:44:45', '2023-06-19 23:44:45');
    

    在这里插入图片描述

  • Step3: 配置pom.xml文件

    • 添加数据源坐标依赖

      • 数据源坐标:druid
      • MyBatisPlus坐标:MyBatisPlus
      • lombok坐标:lombok
      • 分页坐标:mybatis-plus-jsqlparser
      • hutool坐标:hutool-all
      <!--lombok坐标-->
      <dependency>
          <groupId>org.projectlombok</groupId>
          <artifactId>lombok</artifactId>
      </dependency>
      <!--druid坐标-->
      <dependency>
          <groupId>com.alibaba</groupId>
          <artifactId>druid</artifactId>
          <version>1.2.18</version>
      </dependency>
      <!--MyBatisPlus坐标-->
      <dependency>
          <groupId>com.baomidou</groupId>
          <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
          <version>3.5.9</version>
      </dependency>
      <!--分页坐标mybatis-plus-jsqlparser-->
      <dependency>
          <groupId>com.baomidou</groupId>
          <artifactId>mybatis-plus-jsqlparser</artifactId>
          <version>3.5.9</version>
      </dependency>
      <!--hutool坐标-->
      <dependency>
          <groupId>cn.hutool</groupId>
          <artifactId>hutool-all</artifactId>
          <version>5.8.25</version>
      </dependency>
      
    • 配置插件

      • Mockito警告解决
      <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>3.5.2</version>
          <configuration>
              <argLine>-javaagent:"${settings.localRepository}/org/mockito/mockito-core/5.14.2/mockito-core-5.14.2.jar"</argLine>
          </configuration>
      </plugin>
      
  • Step4: 利用代码生成器生成对应的包以及代码,初始项目结构如下

    在这里插入图片描述

  • Step5: 将配置文件application.properties改为application.yml格式的配置文件,并在该配置文件中配置数据库连接信息、数据源信息以及MyBatisPlus的运行日志

    注意:

    ​ 配置MyBatisPlus的运行日志在此处写出来只是为了演示,后续可根据实际情况进行配置,在该快速入门中博主是进行该配置的。

    spring:
      # 配置数据库连接信息以及数据源信息
      datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/mp?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
        username: root
        password: 123456
    
    mybatis-plus:
      configuration:
        # 配置MyBatisPlus的运行日志
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
      # 实体类的别名扫描包---即设置别名
      type-aliases-package: at.guigu.domain.po
    
  • Step6: 在项目MpDemo自带的测试类MpDemoApplicationTests中编写代码,获取到对应的密钥和密文

    package at.guigu;
    
    import com.baomidou.mybatisplus.core.toolkit.AES;
    import org.junit.jupiter.api.Test;
    import org.springframework.boot.test.context.SpringBootTest;
    
    @SpringBootTest
    class MpDemoApplicationTests {
    
        @Test
        void contextLoads() {
            // 生成16位随机AES密钥
            String randomKey = AES.generateRandomKey();
            System.out.println("randomKey:" + randomKey);
            // 利用密码对用户名加密
            String username = AES.encrypt("root", randomKey);
            System.out.println("username:" + username);
            // 利用密钥对密码进行加密
            String password = AES.encrypt("123456", randomKey);
            System.out.println("password:" + password);
        }
    
    }
    

    输出结果为

    randomKey:eAomgIauKAmVZAmD
    username:d4k5Nxb42AqYk5W8w2Jq+A==
    password:58I7EgY9dVRmpDBECFNJWg==
    
  • Step7: 将配置文件中的用户名、密码加密

    spring:
      # 配置数据库连接信息以及数据源信息
      datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/mp?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
        username: mpw:d4k5Nxb42AqYk5W8w2Jq+A==
        password: mpw:58I7EgY9dVRmpDBECFNJWg==
    
    mybatis-plus:
      configuration:
        # 配置MyBatisPlus的运行日志
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
      # 实体类的别名扫描包---即设置别名
      type-aliases-package: at.guigu.domain.po
    
  • Step8: 进行IDEA配置解密相关操作

    步骤略,可详见配置解密部分内容

    注意:单元测试以及引导类均需解密操作,否则无法运行

  • Step9: 创建一个与三层架构包同级的config包并在该包下创建一个MyBatisPlus的配置类MpConfig,代码如下

    • Step9-1: 在该类中创建一个使用@Bean注解的拦截器方法mybatisPlusInterceptor()
    • Step9-2: 在该方法中定义MyBatisPlus的拦截器并添加具体的拦截器—分页拦截器

    注意:DbType 枚举类包括多个数据库类型,用于指定具体的数据库类型。当有多个数据库则可以不配具体类型,否则都建议配上具体的DbType

    在实际项目中,若要在MyBatis的配置类中添加多个插件方法(即定义多个bean),则分页对应的bean必须最后添加

    package at.guigu.config;
    
    import com.baomidou.mybatisplus.annotation.DbType;
    import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
    import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    @MapperScan("at.guigu.mapper")
    public class MpConfig {
    
        /**
         * 添加拦截器方法
         * @return
         */
        @Bean
        public MybatisPlusInterceptor mybatisPlusInterceptor() {
            // 定义MyBatisPlus的拦截器
            MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();
            // 配置分页拦截器---注意:若有多个数据源则可以不配具体类型,否则都建议配上具体的DbType(即数据库)
            mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
            return mpInterceptor;
        }
    }
    
  • Step10: 创建一个与三层架构包同级的enums枚举包,并在该包下创建利用enum关键字声明的枚举类UserStatus,代码如下:

    具体解释可详见枚举处理器部分内容

    package at.guigu.enums;
    
    import com.baomidou.mybatisplus.annotation.EnumValue;
    import lombok.Getter;
    
    @Getter
    @AllArgsConstructor
    public enum UserStatus {
        // 定义枚举类本类主要属性
        FROZEN(0, "禁止使用"),
        NORMAL(1, "已激活"),
        ;
        // 定义枚举类其它普通属性
        @EnumValue
        private int value;
        private String desc;
    
    }
    
  • Step11: 更改实体类User中属性status的数据类型为枚举类

    在这里插入图片描述

  • Step12: 在配置文件中配置默认的枚举类型处理器 (default-enum-type-handler),完整配置文件application.yml代码如下

    spring:
      # 配置数据库连接信息以及数据源信息
      datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/mp?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
        username: mpw:d4k5Nxb42AqYk5W8w2Jq+A==
        password: mpw:58I7EgY9dVRmpDBECFNJWg==
    
    mybatis-plus:
      configuration:
        # 配置MyBatisPlus的运行日志
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
        # 配置默认的枚举类型处理器
        default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
      # 实体类的别名扫描包---即设置别名
      type-aliases-package: at.guigu.domain.po
    
  • Step13: 在po包下创建UserInfo类,代码如下:

    具体解释可详见JSON处理器部分内容

    package at.guigu.domain.po;
    
    import lombok.Data;
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor(staticName = "fo")
    public class UserInfo {
        //年龄
        private Integer age;
        //简介
        private String intro;
        //性别
        private String gender;
    }
    
  • Step14: 将实体类User中的info属性的类型由String改为UserInfo

    在这里插入图片描述

  • Step15: 继续更改User实体类代码

    • 给实体类中info属性添加@TableField注解,并给该注解添加JSON的类型处理器,代码如下:
    • 给实体类的@TableName注解添加autoResultMap属性,且值设为true
      • autoResultMap:是否自动构建 resultMap 并使用,默认为false
    package at.guigu.domain.po;
    
    import at.guigu.enums.UserStatus;
    import com.baomidou.mybatisplus.annotation.TableField;
    import com.baomidou.mybatisplus.annotation.TableName;
    import com.baomidou.mybatisplus.annotation.IdType;
    import com.baomidou.mybatisplus.annotation.TableId;
    import java.time.LocalDateTime;
    import java.io.Serializable;
    
    import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
    import lombok.Data;
    import lombok.EqualsAndHashCode;
    import lombok.experimental.Accessors;
    
    @Data
    @EqualsAndHashCode(callSuper = false)
    @Accessors(chain = true)
    @TableName(value = "user", autoResultMap = true)
    public class User implements Serializable {
    
        private static final long serialVersionUID = 1L;
        
        // 用户id
        @TableId(value = "id", type = IdType.AUTO)
        private Long id;
        // 用户名
        private String username;
        // 密码
        private String password;
        // 注册手机号
        private String phone;
        // 详细信息
        // 利用@TableField注解的typeHandler属性配置JSON的类型处理器
        @TableField(typeHandler = JacksonTypeHandler.class)
        private UserInfo info;
        // 使用状态(1正常 2冻结)
        private UserStatus status;
        // 账户余额
        private Integer balance;
    	// 创建时间
        private LocalDateTime createTime;
        // 更新时间
        private LocalDateTime updateTime;
    
    }
    

环境准备二

由于请求参数实体为JSON格式,所以需要定义一个请求参数实体

在这里插入图片描述

  • Step1: 在domain包下创建query包,并在该包下创建分页查询的请求参数实体PageQuery,代码如下

    注意:

    ​ 前端的请求参数实体放在query包下;

    ​ 写分页参数实体时,需要给定页码、每页数据量、是否升序排列默认值;

    ​ 而需要排序的字段则由用户指定。

    package at.guigu.domain.query;
    
    import lombok.Data;
    
    /**
     * 分页查询的请求参数实体
     */
    @Data
    public class PageQuery {
        // 当前页码
        private Integer pageNo = 1;
        // 每页显示的数据量
        private Integer pageSize = 5;
        // 定义需要排序的字段
        private String sortBy;
        // 是否升序排列
        private Boolean isAsc = false;
    }
    

由于返回结果返回的是JSON对象,所以需要定义一个返回结果实体 ,又因为list属性值为集合,集合中也是JSON对象。所以一共需要定义两个返回结果实体

在这里插入图片描述

  • Step1: 在domain包下创建vo包,并在该包下创建分页查询的返回结果实体类PageVo,代码如下

    注意:

    ​ 返回给前端的结果实体放在vo包下;

    package at.guigu.domain.vo;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    import java.util.List;
    @Data
    @AllArgsConstructor(staticName = "of")
    @NoArgsConstructor
    public class PageVo<T> {
        private  Long total;
        private  Long Pages;
        private List<T> list;
    }
    
  • Step2: 在vo包下创建返回结果实体类UserVo,代码如下:

    package at.guigu.domain.vo;
    
    import at.guigu.domain.po.UserInfo;
    import at.guigu.enums.UserStatus;
    import com.baomidou.mybatisplus.annotation.TableField;
    import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
    import lombok.Data;
    
    @Data
    public class UserVo {
        // 用户id
        private Integer id;
        // 用户名
        private String username;
        /**
         * 详细信息
         * 利用@TableField注解的typeHandler属性配置JSON的类型处理器
         */
        @TableField(typeHandler = JacksonTypeHandler.class)
        private UserInfo info;
        /**
         * 使用状态(1正常 2冻结)
         */
        private UserStatus status;
        // 账户余额
        private Integer balance;
    }
    

业务层

  • Step1:IUserService接口中写入分页查询的方法queryUserByPage

    package at.guigu.service;
    
    import at.guigu.domain.po.User;
    import at.guigu.domain.query.PageQuery;
    import at.guigu.domain.vo.PageVo;
    import at.guigu.domain.vo.UserVo;
    import com.baomidou.mybatisplus.extension.service.IService;
    
    public interface IUserService extends IService<User> {
    	// 分页查询方法
        PageVo<UserVo> queryUserByPage(PageQuery pageQuery);
    }
    
  • Step2:IUserService接口的实现类UserServiceImpl中重写分页查询方法queryUserByPage

    package at.guigu.service.impl;
    
    import at.guigu.domain.po.User;
    import at.guigu.domain.query.PageQuery;
    import at.guigu.domain.vo.PageVo;
    import at.guigu.domain.vo.UserVo;
    import at.guigu.mapper.UserMapper;
    import at.guigu.service.IUserService;
    import cn.hutool.core.bean.BeanUtil;
    import cn.hutool.core.collection.CollUtil;
    import cn.hutool.core.util.StrUtil;
    import com.baomidou.mybatisplus.core.metadata.OrderItem;
    import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import org.springframework.stereotype.Service;
    
    import java.util.Collections;
    import java.util.List;
    
    /**
     * <p>
     * 用户表 服务实现类
     * </p>
     *
     * @author cgrs572
     * @since 2024-12-30
     */
    @Service
    public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
        @Override
        public PageVo<UserVo> queryUserByPage(PageQuery pageQuery) {
            // 1 构建分页条件
            Page<User> p = Page.of(pageQuery.getPageNo(), pageQuery.getPageSize());
            // 2 根据指定参数进行升序排列
            // 若获取的字符串不为null,且不为空则按照排序字段排序
            if (StrUtil.isNotBlank(pageQuery.getSortBy())) {
                // 若排序字段不为空,则按照排序字段排序
                if (pageQuery.getIsAsc()) p.addOrder(OrderItem.ascs(pageQuery.getSortBy()));
                else p.addOrder(OrderItem.descs(pageQuery.getSortBy()));
            } else {
                // 若排序字段为空,默认按照更新时间排序
                p.addOrder(OrderItem.desc("update_time"));
            }
            // 调用UserService中的page方法进行分页查询
            page(p);//等同于this.page(p);
    
            // 获取当前页的所有数据
            List<User> users = p.getRecords();
            // 若当前页无数据则返回空数据
            if (CollUtil.isEmpty(users)) return PageVo.of(p.getTotal(), p.getPages(), Collections.emptyList());
            // 当前页有数据则
            else {
                // 将集合List<User>转换为集合List<UserVo>
                List<UserVo> userVos = BeanUtil.copyToList(users, UserVo.class);
                // 封装返回数据
                return PageVo.of(p.getTotal(), p.getPages(), userVos);
            }
        }
    }
    

表现层

  • Step1: 在controller包下的UserController类中写入初始代码(采用构造器依赖注入IUserService接口对应的Bean)

    • 方式一(推荐):

      • 给需要进行依赖注入的属性添加final关键字(该关键字必须在初始时就给属性赋值,也就是说初始必须给其立马进行构造器依赖注入,否则报错)
      • 此时进行构造器依赖注入的注解可用@RequiredArgsConstructor@AllArgsConstructor(博主使用的是@RequiredArgsConstructor
        • 推荐使用@RequiredArgsConstructor:自动生成一个构造函数,该构造函数接受所有 final 字段和 @NonNull 注解字段。(也就是说该构造器的参数为所有包含final 关键字或 @NonNull 注解的属性)
      @RestController
      @RequestMapping("/user")
      // 构造器依赖注入
      @RequiredArgsConstructor
      public class UserController {
      
          private final IUserService userService;
      
          /**
           * 分页查询
           */
          @GetMapping("/page")
          public PageVo<UserVo> queryUserByPage(PageQuery pageQuery) {
      
          }
      }
      
    • 方式二:

      • 方式二并未给需要进行依赖注入的属性添加final关键字,这样也行,但是在实际开发项目中,controller类中可能需要很多的依赖注入,这就会导致忘记依赖注入,所以尽量不要用方式二
      @RestController
      @RequestMapping("/user")
      // 构造器依赖注入
      @AllArgsConstructor
      public class UserController {
      
          private IUserService userService;
      
          /**
           * 分页查询
           */
          @GetMapping("/page")
          public PageVo<UserVo> queryUserByPage(PageQuery pageQuery) {
      
          }
      }
      
  • Step2: 在UserController类中写入分页查询的代码

    package at.guigu.controller;
    
    
    import at.guigu.domain.query.PageQuery;
    import at.guigu.domain.vo.PageVo;
    import at.guigu.domain.vo.UserVo;
    import at.guigu.service.IUserService;
    import lombok.AllArgsConstructor;
    import lombok.RequiredArgsConstructor;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    @RequestMapping("/user")
    // 构造器依赖注入
    @AllArgsConstructor
    public class UserController {
    
        private IUserService userService;
    
        /**
         * 分页查询
         */
        @GetMapping("/page")
        public PageVo<UserVo> queryUserByPage(PageQuery pageQuery) {
            return userService.queryUserByPage(pageQuery);
        }
    }
    

引导类运行测试

  • 无请求参数

    在这里插入图片描述

  • 存在请求参数

    在这里插入图片描述

  • 注意

    • 在上述实例中已实现了分页查询,若想要实现带条件的分页查询,只需要在query包下创建继承PageQuery类的子类(假设子类为UserPageQuery),然后在该子类中进行条件查询即可(此处不在进行示例)

案例优化

在业务层的实现类UserServiceImpl中,有些代码是固定不变的,所以我们可以对其进行优化,UserServiceImpl类的初始完整代码如下:

package at.guigu.service.impl;

import at.guigu.domain.po.User;
import at.guigu.domain.query.PageQuery;
import at.guigu.domain.vo.PageVo;
import at.guigu.domain.vo.UserVo;
import at.guigu.mapper.UserMapper;
import at.guigu.service.IUserService;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;

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

/**
 * <p>
 * 用户表 服务实现类
 * </p>
 *
 * @author cgrs572
 * @since 2024-12-30
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
    @Override
    public PageVo<UserVo> queryUserByPage(PageQuery pageQuery) {
        // 1 构建分页条件
        Page<User> p = Page.of(pageQuery.getPageNo(), pageQuery.getPageSize());
        // 2 根据指定参数进行升序排列
        // 若获取的字符串不为null,且不为空则按照排序字段排序
        if (StrUtil.isNotBlank(pageQuery.getSortBy())) {
            // 若排序字段不为空,则按照排序字段排序
            if (pageQuery.getIsAsc()) p.addOrder(OrderItem.ascs(pageQuery.getSortBy()));
            else p.addOrder(OrderItem.descs(pageQuery.getSortBy()));
        } else {
            // 若排序字段为空,默认按照更新时间排序
            p.addOrder(OrderItem.desc("update_time"));
        }
        
        // 调用UserService中的page方法进行分页查询
        page(p);//等同于this.page(p);

        // 获取当前页的所有数据
        List<User> users = p.getRecords();
        // 若当前页无数据则返回空数据
        if (CollUtil.isEmpty(users)) return PageVo.of(p.getTotal(), p.getPages(), Collections.emptyList());
        // 当前页有数据则
        else {
            // 将集合List<User>转换为集合List<UserVo>
            List<UserVo> userVos = BeanUtil.copyToList(users, UserVo.class);
            // 封装返回数据
            return PageVo.of(p.getTotal(), p.getPages(), userVos);
        }
    }
}

优化一

优化构建分页条件、根据指定参数进行升序或降序排列。UserServiceImpl类相关的源代码如下:

// 1 构建分页条件
Page<User> p = Page.of(pageQuery.getPageNo(), pageQuery.getPageSize());
// 2 根据指定参数进行升序排列
// 若获取的字符串不为null,且不为空则按照排序字段排序
if (StrUtil.isNotBlank(pageQuery.getSortBy())) {
    // 若排序字段不为空,则按照排序字段排序
    if (pageQuery.getIsAsc()) p.addOrder(OrderItem.ascs(pageQuery.getSortBy()));
    else p.addOrder(OrderItem.descs(pageQuery.getSortBy()));
} else {
    // 若排序字段为空,默认按照更新时间排序
    p.addOrder(OrderItem.desc("update_time"));
}
  • 分页查询的请求参数实体PageQuery类代码如下:

    package at.guigu.domain.query;
    
    import lombok.Data;
    
    /**
     * 分页查询的请求参数实体
     */
    @Data
    public class PageQuery {
        // 当前页码
        private Integer pageNo = 1;
        // 每页显示的数据量
        private Integer pageSize = 5;
        // 定义需要排序的字段
        private String sortBy;
        // 是否升序排列
        private Boolean isAsc = false;
    }
    

    在该PageQuery类中只有成员变量,并没有成员方法,此种情况属于贫血模型,不符合面向对象的思想,所以需要为其加上成员方法(即行为),变为充血模型(符合面向对象的思想)

    所以将 构建分页条件、根据指定参数进行升序或降序排列 的代码封装成一个成员方法写入PageQuery类中

  • Step1:PageQuery类中定义公共的成员方法toMpPage(),完整PageQuery类代码如下:

    package at.guigu.domain.query;
    
    import cn.hutool.core.util.StrUtil;
    import com.baomidou.mybatisplus.core.metadata.OrderItem;
    import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
    import lombok.Data;
    
    /**
     * 分页查询的请求参数实体
     */
    @Data
    public class PageQuery {
        // 当前页码
        private Integer pageNo = 1;
        // 每页显示的数据量
        private Integer pageSize = 5;
        // 定义需要排序的字段
        private String sortBy;
        // 是否升序排列
        private Boolean isAsc = false;
    
        /**
         * 单字段排序
         * 若前端未传来参数则按照该方法的参数进行默认排序
         * @param defaultSortBy
         * @param isAsc
         * @return Page<T>
         * @param <T>
         */
        public <T> Page<T> toMpPage (String defaultSortBy, Boolean isAsc) {
            // 1 构建分页条件
            Page<T> p = Page.of(pageNo, pageSize);
            //2 根据指定参数进行升序排列
            // 若获取的字符串不为null,且不为空则按照排序字段排序
            if (StrUtil.isNotBlank(sortBy)) {
                p.addOrder(this.isAsc ? OrderItem.ascs(sortBy) : OrderItem.descs(sortBy));
            } else {
                // 若排序字段为空,默认按照传入参数排序
                p.addOrder(isAsc ? OrderItem.asc(defaultSortBy) : OrderItem.desc(defaultSortBy));
            }
            return p;
        }
    
    }
    
  • Step2: 业务层的实现类UserServiceImpl代码更改如下:

    package at.guigu.service.impl;
    
    import at.guigu.domain.po.User;
    import at.guigu.domain.query.PageQuery;
    import at.guigu.domain.vo.PageVo;
    import at.guigu.domain.vo.UserVo;
    import at.guigu.mapper.UserMapper;
    import at.guigu.service.IUserService;
    import cn.hutool.core.bean.BeanUtil;
    import cn.hutool.core.collection.CollUtil;
    import cn.hutool.core.util.StrUtil;
    import com.baomidou.mybatisplus.core.metadata.OrderItem;
    import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import org.springframework.stereotype.Service;
    
    import java.util.Collections;
    import java.util.List;
    
    /**
     * <p>
     * 用户表 服务实现类
     * </p>
     *
     * @author cgrs572
     * @since 2024-12-30
     */
    @Service
    public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
        @Override
        public PageVo<UserVo> queryUserByPage(PageQuery pageQuery) {
    
            Page<User> p = pageQuery.toMpPage("update_time", false);
            // 调用UserService中的page方法进行分页查询
            page(p);//等同于this.page(p);
    
            // 获取当前页的所有数据
            List<User> users = p.getRecords();
            // 若当前页无数据则返回空数据
            if (CollUtil.isEmpty(users)) return PageVo.of(p.getTotal(), p.getPages(), Collections.emptyList());
                // 当前页有数据则
            else {
                // 将集合List<User>转换为集合List<UserVo>
                List<UserVo> userVos = BeanUtil.copyToList(users, UserVo.class);
                // 封装返回数据
                return PageVo.of(p.getTotal(), p.getPages(), userVos);
            }
        }
    }
    
  • 运行测试

    • 无请求参数

      在这里插入图片描述

    • 有请求参数

      在这里插入图片描述

优化二

优化将 集合List<User>转换为集合List<UserVo>UserServiceImpl类相关的源代码如下:

// 获取当前页的所有数据
List<User> users = p.getRecords();
// 若当前页无数据则返回空数据
if (CollUtil.isEmpty(users)) return PageVo.of(p.getTotal(), p.getPages(), Collections.emptyList());
// 当前页有数据则
else {
    // 将集合List<User>转换为集合List<UserVo>
    List<UserVo> userVos = BeanUtil.copyToList(users, UserVo.class);
    // 封装返回数据
    return PageVo.of(p.getTotal(), p.getPages(), userVos);
}
  • 分页查询的返回结果实体类PageVo,代码如下

    package at.guigu.domain.vo;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    import java.util.List;
    @Data
    @AllArgsConstructor(staticName = "of")
    @NoArgsConstructor
    public class PageVo<T> {
        private  Long total;
        private  Long Pages;
        private List<T> list;
    }
    
  • Step1:PageVo类中定义构造器方法,完整PageVo类代码如下:

    package at.guigu.domain.vo;
    
    import at.guigu.domain.po.User;
    import cn.hutool.core.bean.BeanUtil;
    import cn.hutool.core.collection.CollUtil;
    import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    import java.util.Collections;
    import java.util.List;
    @Data
    @AllArgsConstructor(staticName = "of")
    @NoArgsConstructor
    public class PageVo<T> {
        private  Long total;
        private  Long Pages;
        private List<T> list;
    
        public <P> PageVo(Page<P> page, Class<T> clazz){
            // 总数据量
            this.total = page.getTotal();
            // 总页数
            this.Pages = page.getPages();
            // 当前页数据集合
            List<P> users = page.getRecords();
            // 若当前页无数据则返回空数据
            if (CollUtil.isEmpty(users)){
                list = Collections.emptyList();
                return;
            }
            // 当前页有数据则 将集合List<P>转换为集合List<T>
            this.list = BeanUtil.copyToList(users, clazz);
        }
    }
    
  • Step2: 业务层的实现类UserServiceImpl代码更改如下:

    package at.guigu.service.impl;
    
    import at.guigu.domain.po.User;
    import at.guigu.domain.query.PageQuery;
    import at.guigu.domain.vo.PageVo;
    import at.guigu.domain.vo.UserVo;
    import at.guigu.mapper.UserMapper;
    import at.guigu.service.IUserService;
    import cn.hutool.core.bean.BeanUtil;
    import cn.hutool.core.collection.CollUtil;
    import cn.hutool.core.util.StrUtil;
    import com.baomidou.mybatisplus.core.metadata.OrderItem;
    import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import org.springframework.stereotype.Service;
    
    import java.util.Collections;
    import java.util.List;
    
    /**
     * <p>
     * 用户表 服务实现类
     * </p>
     *
     * @author cgrs572
     * @since 2024-12-30
     */
    @Service
    public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
        @Override
        public PageVo<UserVo> queryUserByPage(PageQuery pageQuery) {
    
            Page<User> p = pageQuery.toMpPage("update_time", false);
            // 调用UserService中的page方法进行分页查询
            page(p);//等同于this.page(p);
    
            // 封装返回数据
            return new PageVo<>(p, UserVo.class);
        }
    }
    
  • 运行测试

    • 无请求参数

      在这里插入图片描述

    • 有请求参数

      在这里插入图片描述

优化三

在前两个优化中,我们可以从结果中看到名字均完全暴露出来,若在实际开发中想要不完全显示(即张三四显示为张XX或张X四),则需要做以下步骤:

  • Step1:PageVo类中重载构造器方法,传入一个Function参数,完整PageVo类代码如下:

    package at.guigu.domain.vo;
    
    import at.guigu.domain.po.User;
    import cn.hutool.core.bean.BeanUtil;
    import cn.hutool.core.collection.CollUtil;
    import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    import java.util.Collections;
    import java.util.List;
    import java.util.function.Function;
    import java.util.stream.Collectors;
    
    @Data
    @AllArgsConstructor(staticName = "of")
    @NoArgsConstructor
    public class PageVo<T> {
        private  Long total;
        private  Long Pages;
        private List<T> list;
    
        public <P> PageVo(Page<P> page, Class<T> clazz){
            // 总数据量
            this.total = page.getTotal();
            // 总页数
            this.Pages = page.getPages();
            // 当前页数据集合
            List<P> users = page.getRecords();
            // 若当前页无数据则返回空数据
            if (CollUtil.isEmpty(users)){
                list = Collections.emptyList();
                return;
            }
            // 当前页有数据则 将集合List<P>转换为集合List<T>
            this.list = BeanUtil.copyToList(users, clazz);
        }
    
        public <P> PageVo(Page<P> page, Function<P, T> convertor){
            // 总数据量
            this.total = page.getTotal();
            // 总页数
            this.Pages = page.getPages();
            // 当前页数据集合
            List<P> users = page.getRecords();
            // 若当前页无数据则返回空数据
            if (CollUtil.isEmpty(users)){
                list = Collections.emptyList();
                return;
            }
            // 当前页有数据则 将集合List<P>转换为集合List<T>
            this.list = users.stream().map(convertor).collect(Collectors.toList());
        }
    }
    
  • Step2: 业务层的实现类UserServiceImpl代码更改如下:

    package at.guigu.service.impl;
    
    import at.guigu.domain.po.User;
    import at.guigu.domain.query.PageQuery;
    import at.guigu.domain.vo.PageVo;
    import at.guigu.domain.vo.UserVo;
    import at.guigu.mapper.UserMapper;
    import at.guigu.service.IUserService;
    import cn.hutool.core.bean.BeanUtil;
    import cn.hutool.core.collection.CollUtil;
    import cn.hutool.core.util.StrUtil;
    import com.baomidou.mybatisplus.core.metadata.OrderItem;
    import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import org.springframework.stereotype.Service;
    
    import java.util.Collections;
    import java.util.List;
    
    /**
     * <p>
     * 用户表 服务实现类
     * </p>
     *
     * @author cgrs572
     * @since 2024-12-30
     */
    // 优化三:
    @Service
    public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
        @Override
        public PageVo<UserVo> queryUserByPage(PageQuery pageQuery) {
    
            Page<User> p = pageQuery.toMpPage("update_time", false);
            // 调用UserService中的page方法进行分页查询
            page(p);//等同于this.page(p);
    
            // 封装返回数据
            return new PageVo<>(p, user -> {
                UserVo vo = BeanUtil.copyProperties(user, UserVo.class);
                String username = vo.getUsername();
                // 名字长度为1或2时,直接将第二个字符设置为 "*"
                if (username.length() <= 2) {
                    vo.setUsername(username.substring(0) + "*");
                    //等同于vo.setUsername(username.charAt(0) + "*");
                } else {
                // 名字长度大于2时,将中间部分替换为 "*"
                    String masked = username.charAt(0)
                                    + "*".repeat(username.length() - 2)
                                    +username.charAt(username.length() - 1);
                    vo.setUsername(masked);
                }
                return vo;
            });
        }
    }
    
  • 运行测试
    在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IT机器猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值