MybatisPlus-扩展功能

news2025/2/27 5:47:46

代码生成

在使用MybatisPlus以后,基础的MapperServicePO代码相对固定,重复编写也比较麻烦。因此MybatisPlus官方提供了代码生成器根据数据库表结构生成POMapperService等相关代码。只不过代码生成器同样要编码使用,也很麻烦。

这里推荐大家使用一款MybatisPlus的插件,它可以基于图形化界面完成MybatisPlus的代码生成,非常简单。

安装插件

Idea的plugins市场中搜索并安装MyBatisPlus插件:

在这里插入图片描述

然后重启你的Idea即可使用。

使用

刚好数据库中还有一张address表尚未生成对应的实体和mapper等基础代码。我们利用插件生成一下。 首先需要配置数据库地址,在Idea顶部菜单中,找到Tools,选择Config Database

在这里插入图片描述

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

在这里插入图片描述

点击OK保存。

然后再次点击Idea顶部菜单中的other,然后选择Code Generator:

在这里插入图片描述

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

在这里插入图片描述

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

静态工具

有的时候Service之间也会相互调用,比如某些业务在UserService中实现,查adderss信息需要注入AddressService。有些业务在AddressService中实现,查询user信息需要注入UserService,两个Service相互注入就会出现循环依赖的情况。为了避免出现循环依赖问题,MybatisPlus提供一个静态工具类Db,其中的一些静态方法与IService中方法签名基本一致,也可以帮助我们实现CRUD功能。与IService接口的主要区别是,删除和查询除了传id集合还需要传Class掩码指定对象类型完成反射。只有save和update不需要传,因为save和update直接传送对象,对象都有了不需要字节码了:

在这里插入图片描述

示例:

@Test
void testDbGet() {
    User user = Db.getById(1L, User.class);
    System.out.println(user);
}

@Test
void testDbList() {
    // 利用Db实现复杂条件查询
    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");
}

需求:

改造根据id用户查询的接口,查询用户的同时返回用户收货地址列表。

首先,我们要添加一个收货地址的VO对象:

package com.itheima.mp.domain.vo;

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

@Data
@ApiModel(description = "收货地址VO")
public class 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("是否是默认 1默认 0否")
    private Boolean isDefault;

    @ApiModelProperty("备注")
    private String notes;
}

然后,改造原来的UserVO,添加一个地址属性:

在这里插入图片描述

接下来,修改UserController中根据id查询用户的业务接口:

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

由于查询业务复杂,所以要在service层来实现。首先在IUserService中定义方法:

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

然后,在UserServiceImpl中实现该方法:

@Override
public UserVO queryUserAndAddressById(Long userId) {
    // 1.查询用户
    User user = getById(userId);
    if (user == null || user.getStatus() == 2) {
        throw new RuntimeException("用户状态异常!");
    }
    // 2.查询收货地址
    List<Address> addresses = Db.lambdaQuery(Address.class)
            .eq(Address::getUserId, userId)
            .list();
    // 3.处理vo
    // 转user的PO为VO
    UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
    // 转地址的VO
    if(Collutil.isNotEmpty(addresses)){
        userVO.setAddresses(BeanUtil.copyToList(addresses, AddressVO.class));
    }
    return userVO;
}

在查询地址时,我们采用了Db的静态方法,因此避免了注入AddressService,减少了循环依赖的风险。
在这里插入图片描述

再来实现一个功能:

根据id批量查询用户,并查询出用户对应的所有地址。

UserController:

    @ApiOperation("根据id批量查询用户")
    @GetMapping
    public List<UserVo> queryUserById(@ApiParam("用户id集合") @RequestParam("ids") List<Long> ids){
        // 根据service方法实现查询
        return userService.queryUserAndAddressByIds(ids);
    }

IUserService:

List<UserVo> queryUserAndAddressByIds(List<Long> ids);

UserServiceImpl:

    @Override
    public List<UserVo> queryUserAndAddressByIds(List<Long> ids) {
        // 1.批量查询用户信息
        List<User> users = listByIds(ids);
        if(users.isEmpty(users)){
            return null;
        }
        // 2.根据id查询用户的收货地址信息
       List<UserVo> userVos = users.stream().map(user -> {
            // 将user拷贝到vo
            UserVo userVO = BeanUtil.copyProperties(user, UserVo.class);
            List<Address> addressList = Db.lambdaQuery(Address.class)
                    .eq(Address::getUserId, user.getId())
                    .list();
            userVO.setAddress(BeanUtil.copyToList(addressList, AddressVO.class));
            return userVO;
        }).collect(Collectors.toList());

        return userVos;
    }

在循环里面查询的效率很低,可以批量分开查询。

 @Override
    public List<UserVo> queryUserAndAddressByIds(List<Long> ids) {
        // 1.批量查询用户信息
        List<User> users = listByIds(ids);
        if(users.isEmpty(users)){
            return Collections.emptyList();
        }
        // 2.查询地址
        // 2.1 获取用户id集合
        List<Long> ids = users.stream().map(User::getId).collect(Collectors.toList());
        // 2.2 根据用户id查询地址
        List<Address> address = Db.lambdaQuery(Adress.class).in(Address::getUserId, userIds).list();
        // 2.3 转换地址VO
        List<AdressVO> addressVOList = BeanUtil.copyTolist(addresss,AddressVO.class);
        // 2.4用户地址集合分组处理,相同用户的放入一个集合(组)中
        // 创建一个空的Map集合
        Map<Long,List<AddressVO>> addressMap = new HashMap<>(0);
        // 如果地址VO集合不为空,需要根据id分组转换到Map集合中。
        if(CollUtil.isNotEmpty(addressVOList)){
            // 地址Map中,用户id为键,对应的地址集合为值。
            addressMap = addressVOList.stream().collect(Collectors.groupingBy(AdressVO::getUserId));
        }
        // 3.转换VO返回
        List<UserVO> list = new ArrayList<>(users,size());
        for(User user : users){
            // 3.1 转换User的PO为VO
            UserVO vo = BeanUtil.copyProperties(user, UserVO.class);
            list.add(vo);
            // 3.2 转换地址VO
            vo.setAddresses(addressMap.get(user.getId());
        }
        return userVos;
    }

在这里插入图片描述

逻辑删除

对于一些比较重要的数据,我们往往会采用逻辑删除的方案,即:

  • 在表中添加一个字段标记数据是否被删除
  • 当删除数据时把标记置为true
  • 查询时过滤掉标记为true的数据

逻辑删除字段的为deleted:

  • 删除操作为 UPDATE user SET deleted = 1 WHERE id = 1 AND deleted = 0
  • 查询操作: SELECT * FROM user WHERE deleted = 0

一旦采用了逻辑删除,所有的查询和删除逻辑都要跟着变化,非常麻烦。

为了解决这个问题,MybatisPlus就添加了对逻辑删除的支持。

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

添加逻辑删除字段

例如,我们给address表添加一个逻辑删除字段:

alter table address add deleted bit default b'0' null comment '逻辑删除';

然后给Address实体添加deleted字段:

在这里插入图片描述

配置逻辑删除字段

接下来,我们要在application.yml中配置逻辑删除字段:

mybatis-plus:
  type-aliases-package: com.itheima.mp.domain.po
  global-config:
    db-config:
      id-type: auto
      logic-delete-field: deleted #配置逻辑删除字段

测试

创建IAddressService接口的测试类,并执行一个删除操作:

@SpringBootTest
class IAddressServiceTest {

    @Autowired
    private IAddressService addressService;

//    逻辑删除
    @Test
    public void testLogicDelete() {
        // 1.执行逻辑删除操作
        addressService.removeById(60L);
        // 2. 执行查询操作
        addressService.getById(60L);
    }
}

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

在这里插入图片描述

在这里插入图片描述

综上, 开启了逻辑删除功能以后,我们就可以像普通删除一样做CRUD,基本不用考虑代码逻辑问题。还是非常方便的。

注意: 逻辑删除本身也有自己的问题,比如:

  • 会导致数据库表垃圾数据越来越多,从而影响查询效率
  • SQL中全都需要对逻辑删除字段做判断,影响查询效率

因此,我不太推荐采用逻辑删除功能,如果数据不能删除,可以采用把数据迁移到其它表的办法。

通用枚举

User类中有一个用户状态字段:

在这里插入图片描述

像这种字段我们一般会定义一个枚举,做业务判断的时候就可以直接基于枚举做比较。但是我们数据库采用的是int类型,对应的PO也是Integer。因此业务操作时必须手动把枚举Integer转换,非常麻烦。

因此,MybatisPlus提供了一个处理枚举的类型转换器,可以帮我们把枚举类型与数据库类型自动转换

定义枚举

我们定义一个用户状态的枚举:

在这里插入图片描述

代码如下:

package com.itheima.mp.enums;

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

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

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

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

枚举是直接可以使用==比较的,比较status值的时候就可以看user的status是否为NORMAL或者FREEZE。这样就代码中不会有数字代表状态,使用英文单词代表,代码的可读性也就更好。

在这里插入图片描述

数据库表中存储的status依然存储的是INT类型,但是PO是枚举类型。假如有个枚举对象,里面状态是NORMAL,在save的时候往数据库写的时候不能写NORMAL。反之,查询数据库查到的是INT类型,但是转成PO的时候要用枚举。这就存在Java中的枚举类型与数据库中INT类型的相互转换,不光是枚举类型,Java中的所有类型都需要与数据库中的类型进行转换。这些所有的转换底层都是由Mybatis做的,在Mybatis中有个TypeHandler类型处理器,在ibatis包下,里面由很多的数据类型转换的实现。MP在mybatis的基础上做了拓展,加入了MybatisEnumTypeHandler枚举类型处理器以及AbstractJsonTypeHandler的JSON类型处理器。

要让MybatisPlus处理枚举与数据库类型自动转换,我们必须告诉MybatisPlus,枚举中的哪个字段的值作为数据库值。 MybatisPlus提供了@EnumValue注解来标记枚举属性:

在这里插入图片描述

配置枚举处理器

application.yaml文件中添加配置,使枚举处理器生效:

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

测试

需要将其他判断状态的字段都改为枚举类型,同时将返回类型UserVO也改为枚举类型。重新启动程序,前端查看status的返回值。

在这里插入图片描述

JsonValue注解

查到的结果是NORMALFROZEN,证明成功查询,枚举的处理也自动处理好了。如何让其返回1、2或者正常、冻结。这与返回值有关,目前返回值改为枚举类型了,枚举在JSON处理的时候,默认以英文名返回。要想让其以value或者desc返回,就需要告诉SpringMVC因为程序的数据往前端返回时是SpringMVC处理的。SpringMVC处理JSON时,底层用的是jackson json包。jackson json提供一些注解用于标记枚举里面的值,把谁返回。添加注解@JsonValuedesc上就返回desc的值,加在value上就返回value的值

在这里插入图片描述

在这里插入图片描述

@Test
void testService() {
    List<User> list = userService.list();
    list.forEach(System.out::println);
}

总结

如何实现PO类中的枚举类型变量与数据库字段的转换?

  • 给枚举中的与数据库对应value值添加@EnumValue注解

在这里插入图片描述

  • 在配置文件中配置通用的枚举处理器,实现类型转换
    在这里插入图片描述

枚举在给前端返回的时候默认返回枚举项的名字,不够友好,可以通过@JsonValue自定义返回的值。

在这里插入图片描述

JSON类型处理器

MP除了提供枚举类型处理器外,还提供了JSON类型处理器,它就是解决数据库中JSON类型与Java类型的转换的。

数据库的user表中有一个info字段,是JSON类型:

在这里插入图片描述

格式像这样:

{"age": 20, "intro": "佛系青年", "gender": "male"}

在Java中没有JSON这种数据类型,一般在Java中都是用String类型接收。

而目前User实体类中却是String类型:

在这里插入图片描述

在这种情况下不需要做任何操作,Mybatis就能自动进行Java中字符串MySQL里面的JSON处理。但是在处理业务的时候就比较困难,比如从数据库查出Json数据之后,想要取出某个字段信息就不行,因为在java中是字符串接收的。这样一来,我们要读取info中的属性时就非常不方便。如果要方便获取,info的类型最好是一个Map或者实体类。

而一旦我们把info改为对象类型,就需要在写入数据库时手动转为String,再读取数据库时,手动转换为对象,这会非常麻烦。

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

接下来,我们就来看看这个处理器该如何使用。

定义实体

首先,我们定义一个单独实体类来与info字段的属性匹配:

在这里插入图片描述

代码如下:

package com.itheima.mp.domain.po;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor(staticName = "of")
@NoArgsConstructor
public class UserInfo {
    private Integer age;
    private String intro;
    private String gender;
}

使用JSON类型处理器

MP并没有提供在Application.yaml文件里面的全局配置,需要用@TableField注解来处理,在表字段里面设置一个typeHandler属性,在这里指定JacksonTypeHandler,这种配置只针对当前字段有效,如果有好多字段就需要分别指定。同时需要注意将info的类型从String 改为UserInfo

在这里插入图片描述

开启自动ResultMap映射

这是会出现对象的嵌套,User对象里面嵌套了另外一个对象UserInfo,一般对象嵌套都需要定义复杂的resultMap。如果不想定义,就需要在TableName注解上加入属性autoResultMap,默认是关闭的需要开启。

在这里插入图片描述

此时对info赋值需要将之前的json赋值改为用UserInfo里面的静态方法构建。主要主要使用的UserInfo不是糊涂包里的,而是自定义的。

在这里插入图片描述

注意UserVO里面也有info属性,需要将其从String类型转为UserInfo类型。

测试

修改之前的查询结果为json格式

在这里插入图片描述

修改后的查询结果为UserInfo对象

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2261355.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

中后台管理信息系统:Axure12套高效原型设计框架模板全解析

中后台管理信息系统作为企业内部管理的核心支撑&#xff0c;其设计与实现对于提升企业的运营效率与决策能力具有至关重要的作用。为了满足多样化的中后台管理系统开发需求&#xff0c;一套全面、灵活的原型设计方案显得尤为重要。本文将深入探讨中后台管理信息系统通用原型方案…

40 基于单片机的温湿度检测判断系统

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 基于STC89C52单片机&#xff0c;采用dht11温湿度传感器检测温湿度&#xff0c; 通过lcd1602显示屏各个参数&#xff0c;四个按键分别可以增加温湿度的阈值&#xff0c; 如果超过阈值&#xff0c;则…

SAP ABAP-日期格式问题 SAP内部错误,反序列化JSON字符串时发生异常 值 20241215 不是根据 ABAP 的 XML 格式的有效日期

SAP ABAP-日期格式问题 SAP内部错误,反序列化JSON字符串时发生异常 值 20241215 不是根据 ABAP 的 XML 格式的有效日期 在SAP内部用 YYYYMMDD没有问题 外部传入参数

深度学习——激活函数、损失函数、优化器

深度学习——激活函数、损失函数、优化器 1、激活函数1.1、一些常见的激活函数1.1.1、sigmoid1.1.2、softmax1.1.3、tanh1.1.4、ReLU1.1.5、Leaky ReLU1.1.6、PReLU1.1.7、GeLU1.1.8、ELU 1.2、激活函数的特点1.2.1、非线性1.2.2、几乎处处可微1.2.3、计算简单1.2.4、非饱和性1…

YOLOv5-7.0训练过程中出现报错Example: export GIT_PYTHON_REFRESH=quiet

出现报错&#xff1a; This initial message can be silenced or aggravated in the future by setting the $GIT_PYTHON_REFRESH environment variable. Use one of the following values: - quiet|q|silence|s|silent|none|n|0: for no message or exception - warn…

发布/部署WebApi服务器(IIS+.NET8+ASP.NETCore)

CS软件授权注册系统-发布/部署WebApi服务器(IIS.NET8ASP.NETCore) 目录 本文摘要VS2022配置发布VS2022发布WebApiIIS服务器部署WebApi 将程序文件复制到云服务器添加网站配置应用程序池配置dns域名配置端口阿里云ECS服务器配置19980端口配置https协议 (申请ssl证书)测试WebAp…

从零开始:如何在.NET Core Web API中完美配置Swagger文档

目录 新建项目 RestFul Swagger配置 注释展示 版本控制 Token传值 方法封装 新建项目 打开visual studio创建新项目&#xff0c;这里我们选择.net core web api模板&#xff0c;然后输入项目名称及其解决方案创建新项目 这里使用配置一些其他信息&#xff0c;根据自己情…

零基础开始学习鸿蒙开发-基础页面的设计

目录 1.样例图 2.逐项分析 2.1 头顶布局分析&#xff1a;首先我们要把第一行的图标绘制出来&#xff0c;一个左一个右&#xff0c;很明显&#xff0c;需要放在一个Row容器中&#xff0c;具体代码如下&#xff1a; 2.2 和头像同一行的布局&#xff0c;需要注意的是&#xff0c…

vscode借助插件调试OpenFoam的正确的.vscode配置文件

正确的备份文件位置&#xff1a; /home/jie/桌面/理解openfoam/正确的调试爆轰单进程案例/mydebugblastFoam 调试爆轰案例流体 并且工作区和用户区都是openfoam-7版本 问题&#xff1a;F5以debug模式启动后不停在断点 解决方法&#xff1a; 这里备份一下.vsode正确的配置&…

【小白包会的】使用supervisor 管理docker内多进程

使用supervisor 管理docker内多进程 一般情况下&#xff0c;一个docker是仅仅运行一个服务的 但是有的情况中&#xff0c;希望一个docker中运行多个进程&#xff0c;运行多个服务&#xff0c;也就是一个docker容器执行多个服务。 调研了一下&#xff0c;发现可以通过**super…

day11 性能测试(3)——Jmeter 断言+关联

【没有所谓的运气&#x1f36c;&#xff0c;只有绝对的努力✊】 目录 1、复习 2、查看结果树 多个http请求原因分析 3、作业 4、Jmeter断言 4.1 响应断言 4.1.1 案例 4.1.2 小结 4.2 json断言 4.2.1 案例 4.2.2 小结 4.3 断言持续时间 4.3.1 案例 4.3.2 小结 4.…

热更新解决方案3 —— xLua

概述 xLua框架导入和AB包相关准备 xLua导入 其它的导入 C#调用Lua 1.Lua解析器 using System.Collections; using System.Collections.Generic; using UnityEngine; //引用命名空间 using XLua;public class Lesson1_LuaEnv : MonoBehaviour {// Start is called before the fi…

Rk3588 FFmpeg 拉流 RTSP, 硬解码转RGB

RK3588 ,基于FFmpeg, 拉取RTSP,使用 h264_rkmpp 实现硬解码. ⚡️ RK3588 编译ffmpeg参考: Ubuntu x64 架构, 交叉编译aarch64 FFmpeg mpp Code RTSPvoid hardwave_init(AVCo

谷粒商城—分布式高级①.md

1. ELASTICSEARCH 1、安装elastic search dokcer中安装elastic search (1)下载ealastic search和kibana docker pull elasticsearch:7.6.2 docker pull kibana:7.6.2(2)配置 mkdir -p /mydata/elasticsearch/config mkdir -p /mydata/elasticsearch/data echo "h…

OpenCV圆形标定板检测算法findGrid原理详解

OpenCV的findGrid函数检测圆形标定板的流程如下: class CirclesGridClusterFinder {CirclesGridClusterFinder(const CirclesGridClusterFinder&); public:CirclesGridClusterFinder

30. Three.js案例-绘制并渲染圆弧

30. Three.js案例-绘制并渲染圆弧 实现效果 知识点 WebGLRenderer WebGLRenderer 是 Three.js 中用于渲染 3D 场景的核心类。它利用 WebGL 技术在浏览器中渲染 3D 图形。 构造器 new THREE.WebGLRenderer(parameters) 参数类型描述parametersObject可选参数对象&#xff…

STM32F407ZGT6-UCOSIII笔记4:时间片轮转调度

本文学习与程序编写基于 正点原子的 STM32F1 UCOS开发手册 编写熟悉一下 UCOSIII系统的 时间片轮转调度 文章提供测试代码讲解、完整工程下载、测试效果图 目录 解决上文的卡系统问题&#xff1a; 使能时间片轮转调度&#xff1a; 任务初始化定义更改&#xff1a; 文件结构…

谭浩强C++课后练习(更新中)

基于过程的程序设计 第1章 C的初步知识 1. 请根据你的了解&#xff0c;叙述C的特点。C对C有哪些发展? 2. 一个 C程序是由哪几部分构成的?其中的每一部分起什么作用? 3. 从接到一个任务到得到最终结果&#xff0c;一般要经过几个步骤? 4. 请说明编辑、编译、连接的作用…

单元测试知识总结

我们希望每段代码都是自测试的&#xff0c;每次改动之后&#xff0c;都能自动发现对现有功能的影响。 1 测试要求 在对软件单元进行动态测试之前&#xff0c;应对软件单元的源代码进行静态测试&#xff1b; 应建立测试软件单元的环境&#xff0c;如数据准备、桩模块、模拟器…

前后端跨域问题(CROS)

前端 在src中创建util文件&#xff0c;写request.js文件&#xff1a; request.js代码如下&#xff1a; import axios from axios import { ElMessage } from element-plus;const request axios.create({// baseURL: /api, // 注意&#xff01;&#xff01; 这里是全局统一加…