当前位置: 首页 > news >正文

关于Mybatis中,IPage<PO>转换成IPage<VO>的问题

以下是一个比较常见通用的一个查询并且为单表查询,在开发初期,或者项目不是很复杂的时候,或者一开始项目框架就规划好的情况下,通常我们都会封装。

在我们的项目中,这部分代码其实是自动生成的,足以满足大部分的简单系统,尤其是你很喜欢单表查询的时候(个人写代码,比较排斥多表联合查询,性能太低),具体相关的怎么实现,这里不多说,可以参考以往的文章。
SpringBoot集成OpenAPI(Swagger3)和Mybatis-plus代码生成器
https://blog.csdn.net/m0_37892044/article/details/126154714
下面这是我们freemarker自动生成代码的框架代码

@ApiOperation(value = "按照条件进行分页查询${table.comment}")
@PostMapping("/info/page")
public R<IPage<${entity}>> page${entity}Info(@RequestBody PageQuery pageQuery) {IPage<${entity}> page = queryService.queryByPage(${table.serviceName ? substring(1) ? uncap_first}, pageQuery);return R.ok(page);
}

以下是我们项目中自动生成的代码

@ApiOperation(value = "6按照条件进行分页查询数据服务")
@PostMapping("/info/page")
@ApiOperationSupport(order = 6)
public R<IPage<DataService>> pageDataServiceInfo(@RequestBody PageQuery pageQuery) {IPage<DataService> page = queryService.queryByPage(dataServiceService, pageQuery);return R.ok(page);
}

以下是自动生成的接口,返回的分页查询结果。可以发现,其实最基本的分页查询功能已经实现了。但是现在有个问题,当数据在前端展示的时候,可能需要我们进行进一步转换,如下所示,上面部分是框架自动给我们实现的接口,并且能用,但是距离我们想要的有一点点差距。下面是我们实际想要的接口。

在这里插入图片描述
差距主要体现,自动生成的接口完全按照数据库来,例如type数据库存储300050001数字,但是用户在页面肯定是希望看到Mysql这个字符串,同理,请求参数和响应参数示例,在数据库都是一个字符串,并且接口返回的数据,是经过转码以后得字符串,但是用户实际上想看到的是Json对象。

相信大家经常都会遇到诸如此类,利用自己项目搭建的框架,或者分页框架,使用了一些框架自带的查询,但是对于查询结果,还需要做进一步转换才能返回给前端或者第三方。这里我就说一下我的使用感受,也算是寻求下,有没有其他的好的解决方案。

这个问题,我也一直很烦恼,有时候仅需要小改造,就能复用自动生成的代码,有时候为了复用自动生成的代码,结果代码改了一大堆,最后实在忍不了,干脆自己直接实现来的方便和好维护。

方案1:同对象同属性,直接修改原来的属性值

适用场景:就是你分页接口返回的属性和你要进行转换的数据属性是完全一样的。
例如,这里我接口返回分页对象是DataService,转换后也是DataService

修改方式1:直接去修改原来的属性值。例如下面dataViewId是字符串,该ID所对应的name也是字符串,那么直接把name的值赋值到id上即可。简单粗暴,就是代码可读性差,属性字段叫做ID,结果里面是name。

IPage<DataService> page = queryService.queryByPage(dataServiceService, pageQuery);
page.getRecords().forEach(temp->{temp.setDataViewId(dataServiceService.getById(temp.getDataViewId()).getName();)
});

方案2:同对象不同属性,新增属性

修改方式2:为了不引起歧义,或者不方便修改原来的值,就从新增加一个字段,叫做dataViewName,把name属性赋值给dataViewName,如下所示,此外,下图中,数据服务的类型type这个字段明显是不能直接在原来的属性上修改,因为它是Integer 类型,只能赋值正数,因此我们也是新增一个字段typeName,并且均使用@TableField(exist = false)标明,这个不是数据库字段,如果不想返回原来的字段给前端,用@JsonIgnore屏蔽即可。

@ApiModelProperty("数据服务类型")
@TableField("type")
@DicCheck(dicGroup = DicGroupEnum.数据服务类型)
@JsonIgnore
private Integer type;@TableField(exist = false)
private String typeName;@ApiModelProperty("所属视图")
@TableField("data_view_id")
private String dataViewId;@TableField(exist = false)
private String dataViewName;

此事下面代码这样转换。

IPage<DataService> page = queryService.queryByPage(dataServiceService, pageQuery);
page.getRecords().forEach(temp->{temp.setDataViewName(dataServiceService.getById(temp.getDataViewId()).getName())temp.setTypeName(dicTypeService.getById(temp.getType()).getName())
});

疑问:方案1、2大数据量怎么优化

可以发现,我们其实上面的两个例子中,都存在一个问题,那就是在for循环中去转换,正常分页一般也就10页,15页,循环个10次,15次的数据库操作,也不至于有啥大问题,就怕万一有人操作每页5000条,然后你循环个5000次查询数据库,那样肯定不行的。解决问题也简单,要么限制分页查询的,数量的大小,比如最多每页20,不然就得想办法解决掉for循环查数据库。
以下是一种很常见的方式:

优化1:实时查询的本地缓存

其思路就是,根据原始的结果,遍历一遍,找出需要转换的ID,然后在根据ID查询对应的名称,在将名称存入Hash,一般需要3次循环,两次数据库操作(假设只有一个字段转换)。对比优化前,是1次循环,以及一次循环的总数+1次的数据库操作。
比如,数据总量1万,优化前,是循环10001次的数据库操作,以及10000次的set操作。优化后是2次数据库操作,以及10000的add操作,10000次的put操作,10000次的set操作。而三次for循环,其实加起来也就3万次,3万次的这种简单的add,set,put操作,和1万次的操作,时间上几乎不会有差异,但是1万次的数据库操作,其差距是会尤其特别大,大到你怀疑人生。

public R<IPage<DataService>> pageDataServiceInfo(@RequestBody PageQuery pageQuery) {//查询结果中某个字段为ID,但是在页面上需要显示此ID对应的名称IPage<DataService> page = queryService.queryByPage(dataServiceService, pageQuery);//从page结果中,获取所有需要转换的dataViewIdsSet<String> dataViewIds = new HashSet<>();page.getRecords().forEach(temp -> dataViewIds.add(temp.getDataViewId()));//查出dataViewIds对应的名称,存入本地缓存Map<String,String> idToName = new HashMap<>();List<DataService> list = dataServiceService.list(new QueryWrapper<DataService>().in("ID", dataViewIds));list.forEach(temp -> idToName.put(temp.getId(), temp.getName()));//将page结果中的dataViewId值,从新设置成名称page.getRecords().forEach(temp->{temp.setDataViewId(idToName.get(temp.getDataViewId()));return R.ok(page);
}

优化2:分布式缓存替换本地缓存

上述的优化1中,我们每次都是去数据库查询(适用于数据库经常存在数据变化的情况),假设数据不常变化,我们可以考虑使用诸如redis这样的缓存
相对比与上面,其实就是减少了每次去实时查数据库,然后在将查询的结果转成hash,使用缓存数据库以后,可以直接将上述hash直接存到redis。伪代码如下所示。

public R<IPage<DataService>> pageDataServiceInfo(@RequestBody PageQuery pageQuery) {//查询结果中某个字段为ID,但是在页面上需要显示此ID对应的名称IPage<DataService> page = queryService.queryByPage(dataServiceService, pageQuery);Map<String,String> idToName = redisService.getStrMap("data_view_id");//将page结果中的dataViewId值,从新设置成名称page.getRecords().forEach(temp->{temp.setDataViewId(idToName.get(temp.getDataViewId()));return R.ok(page);
}

优化3:枚举替换分布式/本地缓存

这种情况适用于,数据两较少,并且万年不变的这种,例如,性别,状态,类型,这种字典类型的数据。

public R<IPage<DataService>> pageDataServiceInfo(@RequestBody PageQuery pageQuery) {//查询结果中某个字段为ID,但是在页面上需要显示此ID对应的名称IPage<DataService> page = queryService.queryByPage(dataServiceService, pageQuery);page.getRecords().forEach(temp->{temp.setTypeName(DbTypeEnum.getNameById(temp.getType()));return R.ok(page);
}

上面的情况都是基于IPage转IPage的问题,接下来说IPage转IPage的问题

方案3:不同对象,取出泛型在拷贝

不同对象,也就是说,我们自带的框架查询出来的对象,和我们要返回给前端的对象,就不一样。
我不知道有没有人尝试写过下面的代码。利用框架查询的分页对象,是数据库实体类DataService,而我们要返回给前端的是DataServiceRes,他两可能大部分属性一样,也可能DataServiceRes的属性是DataService的子集,不方便暴露DataService的所有数据,当然也可能是有字段不一样,需要转换,于是我们想能否使用copyProperties方法,将IPage拷贝给IPage,然后在修改IPage

public R<IPage<DataServiceRes>> pageDataServiceInfo(@RequestBody PageQuery pageQuery) {IPage<DataService> page = queryService.queryByPage(dataServiceService, pageQuery);IPage<DataServiceRes> pageVO = new Page<>();BeanUtils.copyProperties(page, pageVO);pageVO.getRecords().forEach(temp->{temp.setTypeName(DbTypeEnum.getNameById(temp.getType()));});return R.ok(pageVO);
}

如果你这样做了,你将得到一个错误

java.lang.ClassCastException:DataService cannot be cast to DataServiceRes

当然,你要是把for循环注释掉,你会发现他不报错了。debug看一下,就会发现,虽然我们new的page是DataServiceRes,但是发现拷贝过来以后,是DataService
在这里插入图片描述
因为,IPage本身是泛型的,我们使用的
org.springframework.beans.BeanUtils.copyProperties,是没办法办法进行泛型的拷贝,因为是通过反射进行赋值的。以如下的源码所示,因此换个思路。既然无法拷贝泛型。那我们先取出泛型,在拷贝。

所以方法就是,从新创建一个对象,把原来对象的数据在复制过去,复制的时候,要避免泛型的出现,如果某个参数是泛型,那就先把这个参数取出来,创建一个新的参数去替换这个泛型参数。
在这里插入图片描述

public R<IPage<DataServiceRes>> pageDataServiceInfo(@RequestBody PageQuery pageQuery) {IPage<DataService> page = queryService.queryByPage(dataServiceService, pageQuery);//先通过属性拷贝,把page中的非泛型的分页参数拷贝到pageVOIPage<DataServiceRes> pageVO = new Page<>();BeanUtils.copyProperties(page, pageVO);//通过page.getRecords()取出泛型数据List<DataService> records = page.getRecords();//将List<DataService> records拷贝到List<DataServiceRes> listRes(此时没有泛型了)List<DataServiceRes> listRes = CopyUtils.copyList(records, DataServiceRes.class);//修改转义需要的字段listRes.forEach(temp-> {temp.setDataViewId("大王叫我来巡山");});//替换pageVO中的RecordspageVO.setRecords(listRes);return R.ok(pageVO);
}

CopyUtils.copyList()也是用的BeanUtils.copyProperties();

public static <T> List<T> copyList(List<?> listSource,Class<T> targetClazz){if (listSource == null){return new ArrayList<>();}List<T> listTarget = new ArrayList<>(listSource.size());listSource.forEach(temp ->{T newInstance;try {newInstance = targetClazz.newInstance();BeanUtils.copyProperties(temp, newInstance);listTarget.add(newInstance);} catch (Exception e) {e.printStackTrace();} });return listTarget;
}

结果如下
在这里插入图片描述

方案4:Mybatis自带的convert

如下所示,Mybatis给我们提供了convert方法,我们可以在convert方法中返回转换后的对象

public R<IPage<DataServiceRes>> pageDataServiceInfo(@RequestBody PageQuery pageQuery) {IPage<DataService> page = queryService.queryByPage(dataServiceService, pageQuery);IPage<DataServiceRes> pageVO = page.convert(t->{/*DataServiceRes s = new DataServiceRes();BeanUtils.copyProperties(t, s);*/DataServiceRes s = CopyUtils.copyBean(t, DataServiceRes.class);s.setReqExam(JsonUtils.strToJavaBean(t.getReqExam(),Object.class));s.setResExam(JsonUtils.strToJavaBean(t.getResExam(),Object.class));return s;});return R.ok(pageVO);
}

方案5:重构大法

改不下去了,干脆自己最后自己去从新写自己特定业务的分页查询了

相关文章:

关于Mybatis中,IPage<PO>转换成IPage<VO>的问题

以下是一个比较常见通用的一个查询并且为单表查询&#xff0c;在开发初期&#xff0c;或者项目不是很复杂的时候&#xff0c;或者一开始项目框架就规划好的情况下&#xff0c;通常我们都会封装。 在我们的项目中&#xff0c;这部分代码其实是自动生成的&#xff0c;足以满足大…...

使用idea和vecode创建vue项目并启动(超详细)

一、idea创建vue项目 创建项目之前先下载好插件 新建项目找到vue生成器 写好名称&#xff0c;找到自己需要存放的地址&#xff0c;node解释器安装方式可以看我上一个博客&#xff0c;vueCLI是选择vue的版本&#xff0c;我们可以使用idea自带的vue版本默认是vue3&#xff0c;创…...

C#|.net core 基础 - 删除字符串最后一个字符的七大类N种实现方式

今天想通过和大家分享如何删除字符串最后一个字符的N种实现方法&#xff0c;来回顾一些基础知识点。 01第一类、字符串方式 这类方法是通过string类型自身方法直接实现。 1、Substring方法 相信大多数人第一个想到的可能就是这个方法。Substring方法是字符串内置方法&#…...

成都睿明智科技有限公司怎么样靠谱吗?

随着短视频与直播的深度融合&#xff0c;抖音电商凭借其强大的流量入口、精准的算法推荐以及便捷的购物体验&#xff0c;迅速崛起。对于传统企业和新兴品牌而言&#xff0c;这无疑是一个不可多得的机遇。然而&#xff0c;如何在这片红海中脱颖而出&#xff0c;就需要借助专业的…...

docker简述

1.安装dockers&#xff0c;配置docker软件仓库 安装&#xff0c;可能需要开代理&#xff0c;这里我提前使用了下好的包安装 启动docker systemctl enable --now docker查看是否安装成功 2.简单命令 拉取镜像&#xff0c;也可以提前下载使用以下命令上传 docker load -i imag…...

第27周:Transformer实战:文本分类

目录 前言 一、前期准备 1.1 环境安装 1.2 加载数据 二、数据预处理 2.1 构建词典 2.2 生成数据批次和迭代器 2.3 构建数据集 三、模型构建 3.1 定义位置编码器 3.2 定义Transformer模型 3.3 初始化模型 3.4 定义训练函数 3.5 定义评估函数 四、训练模型 4.1 模…...

在QT中将Widget提升为自定义的Widget后,无法设置Widget的背景颜色问题解决方法

一、问题 在Qt中将QWidget组件提升为自定义的QWidget后&#xff0c;Widget设置的样式失效&#xff0c;例如设置背景颜色为白色失效。 二、解决方法 将已经提升的QWidget实例对象&#xff0c;脱离父窗体的样式&#xff0c;然后再重新设置自己的样式。...

【学习笔记】手写一个简单的 Spring IOC

目录 一、什么是 Spring IOC&#xff1f; 二、IOC 的作用 1. IOC 怎么知道要创建哪些对象呢&#xff1f; 2. 创建出来的对象放在哪儿&#xff1f; 3. 创建出来的对象如果有属性&#xff0c;如何给属性赋值&#xff1f; 三、实现步骤 1. 创建自定义注解 2. 创建 IOC 容器…...

日记学习小迪安全27

感觉复制粘贴没有意思&#xff0c;而且还有点浪费时间&#xff0c;主要是学习&#xff0c;不是复制&#xff0c;那就复制别人的吧 第27关就参考这篇文章吧&#xff0c;以下大部分内容都是参考以下文章&#xff08;侵权删除&#xff09; 第27天&#xff1a;WEB攻防-通用漏洞&a…...

【React】类组件和函数组件

构建组件的方式 函数式组件&#xff08;function&#xff09;createElement&#xff08;不建议使用&#xff09;类组件形式创建&#xff08;不建议使用&#xff09; 对于 React 的理解 React, 用于构建用户界面的JavaScript库&#xff0c;本身只提供了Ul层面的解决方案。&am…...

Spring Boot应用开发

Spring Boot是一个基于Spring框架的开源Java框架&#xff0c;旨在简化新Spring应用的初始化和开发过程。它通过提供各种默认配置&#xff0c;减少了繁琐的配置&#xff0c;使开发者能够专注于业务逻辑的实现。本文将介绍Spring Boot的基本概念、优点、关键特性以及如何构建一个…...

mysql事务使用和事务隔离级别与sqlserver的比较

在 MySQL 中&#xff0c;事务 (Transaction) 是一个将一组 SQL 语句作为一个整体执行的机制。事务确保要么所有操作都执行成功&#xff0c;要么在遇到错误时回滚到之前的状态&#xff0c;从而保证数据库数据的一致性和完整性。 事务的四大特性&#xff08;ACID&#xff09; 事…...

双光吊舱图像采集详解!

一、图像采集 可见光图像采集&#xff1a; 使用高性能的可见光相机&#xff0c;通过镜头捕捉自然光或人工光源照射下的目标图像。 相机内部通常配备有先进的图像传感器&#xff0c;如CMOS或CCD&#xff0c;用于将光信号转换为电信号。 红外图像采集&#xff1a; 利用红外热…...

1688商品详情关键词数据-API

要利用 Python 爬虫采集 1688 商品详情数据&#xff0c;需要先了解 1688 网站的页面结构和数据请求方式。一般使用 requests 库请求网站的数据&#xff0c;使用 BeautifulSoup 库解析网页中的数据。 以下是一个简单的 Python 爬虫采集 1688 商品详情数据的示例代码&#xff1a…...

vue 的属性绑定

双大括号不能在 HTML attributes 中使用。想要响应式地绑定一个 attribute&#xff0c;应该使用 v-bind 指令。 <template> <div v-bind:class"boxClass" v-bind:id"boxId"> </div> </template><script> export default{da…...

【附源码】Python :打家劫舍

系列文章目录 Python 算法学习&#xff1a;打家劫舍问题 文章目录 系列文章目录一、算法需求二、解题思路三、具体方法源码方法1&#xff1a;动态规划&#xff08;自底向上&#xff09;方法2&#xff1a;动态规划&#xff08;自顶向下&#xff09;方法3&#xff1a;优化的动态…...

YOLO11改进 | 注意力机制| 对小目标友好的BiFormer【CVPR2023】

秋招面试专栏推荐 &#xff1a;深度学习算法工程师面试问题总结【百面算法工程师】——点击即可跳转 &#x1f4a1;&#x1f4a1;&#x1f4a1;本专栏所有程序均经过测试&#xff0c;可成功执行&#x1f4a1;&#x1f4a1;&#x1f4a1; 本文介绍了一种新颖的动态稀疏注意力机制…...

高级Python开发工程师的面试备考指南

目录 博客标题:高级Python开发工程师的面试备考指南:30个面试问题与详细解析岗位职责问题解析1. 公司产品功能开发和代码维护2. 技术方案与项目计划制定3. 算法基础与代码优化4. 项目管理与团队协作任职要求问题解析5. Python 开发经验6. 数据处理相关库(Pandas, Numpy, Mat…...

【Java】JAVA知识总结浅析

Java是一门功能强大的编程语言&#xff0c;广泛应用于多个领域。Java的编程思想&#xff0c;包括面向过程和面向对象编程&#xff0c;Java的发展历史&#xff0c;各版本的特点&#xff0c;JVM原理&#xff0c;数据类型&#xff0c;Java SE与Java EE的区别&#xff0c;应用场景&…...

23-云原生监控系统

├──23-云原生监控系统 | ├──1-Prometheus监控 | | ├──1-二进制方式部署Prometheus监控系统 | | ├──2-二进制方式部署Prometheus监控系统告警 | | ├──3-容器化构建Prometheus监控系统 | | ├──4-容器监控方案CAdvisor | | └──5-k8s监…...

SciencePlots——绘制论文中的图片

文章目录 安装一、风格二、1 资源 安装 # 安装最新版 pip install githttps://github.com/garrettj403/SciencePlots.git# 安装稳定版 pip install SciencePlots一、风格 简单好用的深度学习论文绘图专用工具包–Science Plot 二、 1 资源 论文绘图神器来了&#xff1a;一行…...

相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解

【关注我&#xff0c;后续持续新增专题博文&#xff0c;谢谢&#xff01;&#xff01;&#xff01;】 上一篇我们讲了&#xff1a; 这一篇我们开始讲&#xff1a; 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下&#xff1a; 一、场景操作步骤 操作步…...

在rocky linux 9.5上在线安装 docker

前面是指南&#xff0c;后面是日志 sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo dnf install docker-ce docker-ce-cli containerd.io -y docker version sudo systemctl start docker sudo systemctl status docker …...

AtCoder 第409​场初级竞赛 A~E题解

A Conflict 【题目链接】 原题链接&#xff1a;A - Conflict 【考点】 枚举 【题目大意】 找到是否有两人都想要的物品。 【解析】 遍历两端字符串&#xff0c;只有在同时为 o 时输出 Yes 并结束程序&#xff0c;否则输出 No。 【难度】 GESP三级 【代码参考】 #i…...

从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路

进入2025年以来&#xff0c;尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断&#xff0c;但全球市场热度依然高涨&#xff0c;入局者持续增加。 以国内市场为例&#xff0c;天眼查专业版数据显示&#xff0c;截至5月底&#xff0c;我国现存在业、存续状态的机器人相关企…...

Linux云原生安全:零信任架构与机密计算

Linux云原生安全&#xff1a;零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言&#xff1a;云原生安全的范式革命 随着云原生技术的普及&#xff0c;安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测&#xff0c;到2025年&#xff0c;零信任架构将成为超…...

Spring数据访问模块设计

前面我们已经完成了IoC和web模块的设计&#xff0c;聪明的码友立马就知道了&#xff0c;该到数据访问模块了&#xff0c;要不就这俩玩个6啊&#xff0c;查库势在必行&#xff0c;至此&#xff0c;它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据&#xff08;数据库、No…...

探索Selenium:自动化测试的神奇钥匙

目录 一、Selenium 是什么1.1 定义与概念1.2 发展历程1.3 功能概述 二、Selenium 工作原理剖析2.1 架构组成2.2 工作流程2.3 通信机制 三、Selenium 的优势3.1 跨浏览器与平台支持3.2 丰富的语言支持3.3 强大的社区支持 四、Selenium 的应用场景4.1 Web 应用自动化测试4.2 数据…...

libfmt: 现代C++的格式化工具库介绍与酷炫功能

libfmt: 现代C的格式化工具库介绍与酷炫功能 libfmt 是一个开源的C格式化库&#xff0c;提供了高效、安全的文本格式化功能&#xff0c;是C20中引入的std::format的基础实现。它比传统的printf和iostream更安全、更灵活、性能更好。 基本介绍 主要特点 类型安全&#xff1a…...

智能职业发展系统:AI驱动的职业规划平台技术解析

智能职业发展系统&#xff1a;AI驱动的职业规划平台技术解析 引言&#xff1a;数字时代的职业革命 在当今瞬息万变的就业市场中&#xff0c;传统的职业规划方法已无法满足个人和企业的需求。据统计&#xff0c;全球每年有超过2亿人面临职业转型困境&#xff0c;而企业也因此遭…...