当前位置: 首页 > 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监…...

基于算法竞赛的c++编程(28)结构体的进阶应用

结构体的嵌套与复杂数据组织 在C中&#xff0c;结构体可以嵌套使用&#xff0c;形成更复杂的数据结构。例如&#xff0c;可以通过嵌套结构体描述多层级数据关系&#xff1a; struct Address {string city;string street;int zipCode; };struct Employee {string name;int id;…...

React 第五十五节 Router 中 useAsyncError的使用详解

前言 useAsyncError 是 React Router v6.4 引入的一个钩子&#xff0c;用于处理异步操作&#xff08;如数据加载&#xff09;中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误&#xff1a;捕获在 loader 或 action 中发生的异步错误替…...

前端倒计时误差!

提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...

linux 错误码总结

1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...

学习STC51单片机31(芯片为STC89C52RCRC)OLED显示屏1

每日一言 生活的美好&#xff0c;总是藏在那些你咬牙坚持的日子里。 硬件&#xff1a;OLED 以后要用到OLED的时候找到这个文件 OLED的设备地址 SSD1306"SSD" 是品牌缩写&#xff0c;"1306" 是产品编号。 驱动 OLED 屏幕的 IIC 总线数据传输格式 示意图 …...

土地利用/土地覆盖遥感解译与基于CLUE模型未来变化情景预测;从基础到高级,涵盖ArcGIS数据处理、ENVI遥感解译与CLUE模型情景模拟等

&#x1f50d; 土地利用/土地覆盖数据是生态、环境和气象等诸多领域模型的关键输入参数。通过遥感影像解译技术&#xff0c;可以精准获取历史或当前任何一个区域的土地利用/土地覆盖情况。这些数据不仅能够用于评估区域生态环境的变化趋势&#xff0c;还能有效评价重大生态工程…...

相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)

【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...

【HTML-16】深入理解HTML中的块元素与行内元素

HTML元素根据其显示特性可以分为两大类&#xff1a;块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...

学校时钟系统,标准考场时钟系统,AI亮相2025高考,赛思时钟系统为教育公平筑起“精准防线”

2025年#高考 将在近日拉开帷幕&#xff0c;#AI 监考一度冲上热搜。当AI深度融入高考&#xff0c;#时间同步 不再是辅助功能&#xff0c;而是决定AI监考系统成败的“生命线”。 AI亮相2025高考&#xff0c;40种异常行为0.5秒精准识别 2025年高考即将拉开帷幕&#xff0c;江西、…...

html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码

目录 一、&#x1f468;‍&#x1f393;网站题目 二、✍️网站描述 三、&#x1f4da;网站介绍 四、&#x1f310;网站效果 五、&#x1fa93; 代码实现 &#x1f9f1;HTML 六、&#x1f947; 如何让学习不再盲目 七、&#x1f381;更多干货 一、&#x1f468;‍&#x1f…...