【Mybatis-Plus】——第二章 快速入门与核心功能(下)

目录

[3. 扩展功能](#3. 扩展功能)

[3.1. 代码生成](#3.1. 代码生成)

[3.1.1 安装插件](#3.1.1 安装插件)

[3.1.2 使用步骤](#3.1.2 使用步骤)

[3.2. 静态工具 Db](#3.2. 静态工具 Db)

实战案例:查询用户及收货地址

[3.3. 逻辑删除](#3.3. 逻辑删除)

配置步骤

测试

[3.4. 通用枚举](#3.4. 通用枚举)

[3.4.1 定义枚举](#3.4.1 定义枚举)

[3.4.2 修改实体类](#3.4.2 修改实体类)

[3.4.3 配置枚举处理器](#3.4.3 配置枚举处理器)

[3.4.4 测试](#3.4.4 测试)

[3.5. JSON 类型处理器](#3.5. JSON 类型处理器)

[3.5.1 定义实体](#3.5.1 定义实体)

[3.5.2 使用类型处理器](#3.5.2 使用类型处理器)

[3.5.3 修改 VO](#3.5.3 修改 VO)

[3.6. 配置加密(选学)](#3.6. 配置加密(选学))

[3.6.1 生成密钥](#3.6.1 生成密钥)

[3.6.2 修改配置](#3.6.2 修改配置)

[3.6.3 测试](#3.6.3 测试)

[4. 插件功能](#4. 插件功能)

[4.1. 分页插件](#4.1. 分页插件)

[4.1.1 配置分页插件](#4.1.1 配置分页插件)

[4.1.2 分页 API](#4.1.2 分页 API)

[4.2. 通用分页实体](#4.2. 通用分页实体)

[4.2.1 定义实体](#4.2.1 定义实体)

[4.2.2 开发接口](#4.2.2 开发接口)


本文介绍了MyBatisPlus的扩展功能和插件使用。主要包括:

1)使用IDEA插件简化代码生成;

2)通过Db静态工具解决循环依赖问题;

3)逻辑删除配置及注意事项;

4)枚举类型处理器的应用;

5)JSON字段处理方案;

6)配置文件加密方法。插件部分重点讲解了分页插件的配置使用,并展示了通用分页实体的实现方案,包括分页条件封装、结果转换等。这些功能可显著提升开发效率,简化CRUD操作,建议根据项目需求选择性使用。

3. 扩展功能

3.1. 代码生成

MyBatis Plus 官方提供了代码生成器,但使用起来比较麻烦。推荐使用 IDEA 插件:

3.1.1 安装插件

在 IDEA 的 plugins 市场中搜索并安装 MyBatis Plus 插件:

然后重启 IDEA 即可使用。

3.1.2 使用步骤
  1. 配置数据库连接 :在 IDEA 顶部菜单中,找到 OtherConfig Database

在弹出的窗口中填写数据库连接的基本信息:

点击 OK 保存。

  1. 生成代码 :再次点击 IDEA 顶部菜单中的 Other,然后选择 Code Generator

在弹出的表单中填写信息:

最终,代码自动生成到指定的位置了;

3.2. 静态工具 Db

Service 之间相互调用可能出现循环依赖问题,MyBatis Plus 提供了静态工具类 Db

示例代码如下:

复制代码
@Test
void testDbGet() {
    User user = Db.getById(1L, User.class);
    System.out.println(user);
}
​
@Test
void testDbList() {
    List<User> list = Db.lambdaQuery(User.class)
            .like(User::getUsername, "o")
            .ge(User::getBalance, 1000)
            .list();
    list.forEach(System.out::println);
}
​
@Test
void testDbUpdate() {
    Db.lambdaUpdate(User.class)
            .set(User::getBalance, 2000)
            .eq(User::getUsername, "Rose");
}
实战案例:查询用户及收货地址

定义 AddressVO

java 复制代码
@Data
@ApiModel(description = "收货地址VO")
public class AddressVO {
    @ApiModelProperty("id") private Long id;
    @ApiModelProperty("用户ID") private Long userId;
    @ApiModelProperty("省") private String province;
    @ApiModelProperty("市") private String city;
    @ApiModelProperty("县/区") private String town;
    @ApiModelProperty("手机") private String mobile;
    @ApiModelProperty("详细地址") private String street;
    @ApiModelProperty("联系人") private String contact;
    @ApiModelProperty("是否是默认") private Boolean isDefault;
    @ApiModelProperty("备注") private String notes;
}

改造 UserVO,添加地址属性:

java 复制代码
@Data
@ApiModel(description = "用户VO实体")
public class UserVO {
    // ... 原有属性
    @ApiModelProperty("收货地址列表")
    private List<AddressVO> addresses;
}

在 Service 中实现查询:

java 复制代码
@Override
public UserVO queryUserAndAddressById(Long userId) {
    User user = getById(userId);
    if (user == null) {
        return null;
    }
    List<Address> addresses = Db.lambdaQuery(Address.class)
            .eq(Address::getUserId, userId)
            .list();
    UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
    userVO.setAddresses(BeanUtil.copyToList(addresses, AddressVO.class));
    return userVO;
}

3.3. 逻辑删除

对于重要数据,采用逻辑删除方案(标记删除而非物理删除)。

配置步骤
  1. 添加逻辑删除字段
java 复制代码
ALTER TABLE address ADD deleted BIT DEFAULT b'0' NULL COMMENT '逻辑删除';
  1. 在实体类中添加字段
java 复制代码
@TableLogic
private Integer deleted;
  1. 在配置文件中配置
java 复制代码
mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: deleted       # 全局逻辑删除字段名
      logic-delete-value: 1             # 逻辑已删除值
      logic-not-delete-value: 0         # 逻辑未删除值
测试
java 复制代码
@Test
void testDeleteByLogic() {
    addressService.removeById(59L);  // 实际执行 UPDATE address SET deleted=1 WHERE id=59
}

方法与普通删除一样,但底层的 SQL 逻辑变了:

查询一下:

java 复制代码
@Test
void testQuery() {
    List<Address> list = addressService.list();  // 自动过滤已删除数据
    list.forEach(System.out::println);
}

会发现 id 为 59 的没有查询出来,而且 SQL 中也对逻辑删除字段做了判断:

开启了逻辑删除功能以后,就可以像普通删除一样做 CRUD,基本不用考虑代码逻辑问题。

注意:逻辑删除也有缺点(无法真正删除、影响查询性能),建议采用数据迁移到其他表的方案。

3.4. 通用枚举

User 类中有用户状态字段(Integer 类型),业务判断时需要手动转换枚举,非常麻烦。MyBatis Plus 提供了枚举类型转换器。

3.4.1 定义枚举
java 复制代码
package com.example.mp.enums;
​
import com.baomidou.mybatisplus.annotation.EnumValue;
import com.fasterxml.jackson.annotation.JsonValue;
import lombok.Getter;
​
@Getter
public enum UserStatus {
    NORMAL(1, "正常"),
    FREEZE(2, "冻结");
​
    @EnumValue  // 标记数据库存储值
    private final int value;
    
    @JsonValue  // 标记 JSON 序列化展示值
    private final String desc;
​
    UserStatus(int value, String desc) {
        this.value = value;
        this.desc = desc;
    }
}
3.4.2 修改实体类
java 复制代码
public class User {
    // ... 其他字段
    private UserStatus status;  // 将 Integer 改为枚举类型
}
3.4.3 配置枚举处理器
java 复制代码
mybatis-plus:
  configuration:
    default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
3.4.4 测试
java 复制代码
@Test
void testService() {
    List<User> list = userService.list();
    list.forEach(System.out::println);  // status 字段为枚举类型
}

3.5. JSON 类型处理器

数据库的 user 表中有一个 info 字段(JSON 类型),目前是 String 类型,读取属性不方便。MyBatis Plus 提供了 JacksonTypeHandler 处理器。

3.5.1 定义实体
java 复制代码
package com.example.mp.domain.po;
​
import lombok.Data;
​
@Data
public class UserInfo {
    private Integer age;
    private String intro;
    private String gender;
}
3.5.2 使用类型处理器
java 复制代码
@TableName(value = "user", autoResultMap = true)
public class User {
    // ... 其他字段
    @TableField(typeHandler = JacksonTypeHandler.class)
    private UserInfo info;
}
3.5.3 修改 VO
java 复制代码
@Data
public class UserVO {
    // ... 其他字段
    private UserInfo info;  // 将 String 改为 UserInfo 类型
}

3.6. 配置加密(选学)

配置文件中的敏感信息(如数据库密码)建议加密存储。

3.6.1 生成密钥
java 复制代码
@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("MySQL123", randomKey);
    System.out.println("password = " + password);
}
3.6.2 修改配置
java 复制代码
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/mp?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: mpw:QWWVnk1Oal3258x5rVhaeQ==  # 密文以 mpw: 开头
    password: mpw:EUFmeH3cNAzdRGdOQcabWg==
3.6.3 测试

启动项目时添加密钥参数:

java 复制代码
--mpw.key=6234633a66fb399f

单元测试时在测试类注解上配置:

java 复制代码
@SpringBootTest(properties = {"mpw.key=6234633a66fb399f"})

4. 插件功能

MyBatis Plus 提供了很多插件功能,使用多个插件时需注意定义顺序。

4.1. 分页插件

未引入分页插件时,IServiceBaseMapper 中的分页方法无法正常起效。

4.1.1 配置分页插件
java 复制代码
package com.example.mp.config;
​
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
​
@Configuration
public class MybatisConfig {
​
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}
4.1.2 分页 API
java 复制代码
@Test
void testPageQuery() {
    Page<User> p = userService.page(new Page<>(2, 2));
    System.out.println("total = " + p.getTotal());
    System.out.println("pages = " + p.getPages());
    p.getRecords().forEach(System.out::println);
}

分页参数 Page 支持排序:

java 复制代码
int pageNo = 1, pageSize = 5;
Page<User> page = Page.of(pageNo, pageSize);
page.addOrder(new OrderItem("balance", false));  // 按余额降序
userService.page(page);

4.2. 通用分页实体

实现用户分页查询接口:

4.2.1 定义实体

PageQuery(通用分页条件)

java 复制代码
@Data
@ApiModel(description = "分页查询实体")
public class PageQuery {
    @ApiModelProperty("页码") private Long pageNo;
    @ApiModelProperty("每页大小") private Long pageSize;
    @ApiModelProperty("排序字段") private String sortBy;
    @ApiModelProperty("是否升序") private Boolean isAsc;
​
    public <T> Page<T> toMpPage(OrderItem ... orders) {
        Page<T> p = Page.of(pageNo, pageSize);
        if (sortBy != null) {
            p.addOrder(new OrderItem(sortBy, isAsc));
            return p;
        }
        if (orders != null) {
            p.addOrder(orders);
        }
        return p;
    }
​
    public <T> Page<T> toMpPage(String defaultSortBy, boolean isAsc) {
        return this.toMpPage(new OrderItem(defaultSortBy, isAsc));
    }
​
    public <T> Page<T> toMpPageDefaultSortByUpdateTimeDesc() {
        return toMpPage("update_time", false);
    }
}

UserQuery(继承分页条件)

java 复制代码
@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel(description = "用户查询条件实体")
public class UserQuery extends PageQuery {
    @ApiModelProperty("用户名关键字") private String name;
    @ApiModelProperty("用户状态") private Integer status;
    @ApiModelProperty("余额最小值") private Integer minBalance;
    @ApiModelProperty("余额最大值") private Integer maxBalance;
}

PageDTO(分页结果)

java 复制代码
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageDTO<V> {
    @ApiModelProperty("总条数") private Long total;
    @ApiModelProperty("总页数") private Long pages;
    @ApiModelProperty("集合") private List<V> list;
​
    public static <V, P> PageDTO<V> empty(Page<P> p) {
        return new PageDTO<>(p.getTotal(), p.getPages(), Collections.emptyList());
    }
​
    public static <V, P> PageDTO<V> of(Page<P> p, Class<V> voClass) {
        List<P> records = p.getRecords();
        if (records == null || records.size() <= 0) {
            return empty(p);
        }
        List<V> vos = BeanUtil.copyToList(records, voClass);
        return new PageDTO<>(p.getTotal(), p.getPages(), vos);
    }
​
    public static <V, P> PageDTO<V> of(Page<P> p, Function<P, V> convertor) {
        List<P> records = p.getRecords();
        if (records == null || records.size() <= 0) {
            return empty(p);
        }
        List<V> vos = records.stream().map(convertor).collect(Collectors.toList());
        return new PageDTO<>(p.getTotal(), p.getPages(), vos);
    }
}
4.2.2 开发接口
java 复制代码
@GetMapping("/page")
public PageDTO<UserVO> queryUsersPage(UserQuery query) {
    return userService.queryUsersPage(query);
}

Service 实现

java 复制代码
@Override
public PageDTO<UserVO> queryUsersPage(UserQuery query) {
    Page<User> page = query.toMpPageDefaultSortByUpdateTimeDesc();
    page(page);
    return PageDTO.of(page, UserVO.class);
}

自定义转换示例(用户名脱敏):

java 复制代码
@Override
public PageDTO<UserVO> queryUsersPage(UserQuery query) {
    Page<User> page = query.toMpPageDefaultSortByUpdateTimeDesc();
    page(page);
    return PageDTO.of(page, user -> {
        UserVO vo = BeanUtil.copyProperties(user, UserVO.class);
        String username = vo.getUsername();
        vo.setUsername(username.substring(0, username.length() - 2) + "**");
        return vo;
    });
}

总结

MyBatis Plus 通过 BaseMapperIService 简化了单表 CRUD 操作,通过条件构造器实现了复杂查询,通过插件机制扩展了分页等功能。掌握这些核心技能,可以大幅提高开发效率。