QueryDSL 自定义返回结果集类型

2024-02-14 12:38

本文主要是介绍QueryDSL 自定义返回结果集类型,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

QueryDSL 自定义返回结果集

  • 环境
  • 问题
  • 分析
  • 解决
  • 结论

环境

  开发框架使用的是springboot,持久框架使用的是spring data jpa+QueryDSL,具体版本参见下面的pom文件。

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.2.6.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>rm.lesi</groupId><artifactId>mms-server</artifactId><version>0.0.1</version><name>mms-server</name><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><!--QueryDSL支持--><dependency><groupId>com.querydsl</groupId><artifactId>querydsl-apt</artifactId><scope>provided</scope></dependency><dependency><groupId>com.querydsl</groupId><artifactId>querydsl-jpa</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope><exclusions><exclusion><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId></exclusion></exclusions></dependency><!--swagger2支持--><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.9.2</version><exclusions><exclusion><groupId>io.swagger</groupId><artifactId>swagger-annotations</artifactId></exclusion><exclusion><groupId>io.swagger</groupId><artifactId>swagger-models</artifactId></exclusion></exclusions></dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><version>2.9.2</version></dependency><dependency><groupId>io.swagger</groupId><artifactId>swagger-annotations</artifactId><version>1.5.23</version></dependency><dependency><groupId>io.swagger</groupId><artifactId>swagger-models</artifactId><version>1.5.23</version></dependency><dependency><groupId>org.springframework.data</groupId><artifactId>spring-data-commons</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin><plugin><groupId>com.mysema.maven</groupId><artifactId>apt-maven-plugin</artifactId><version>1.1.3</version><executions><execution><phase>generate-sources</phase><goals><goal>process</goal></goals><configuration><outputDirectory>target/generated-sources</outputDirectory>
<processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor></configuration></execution></executions></plugin></plugins></build>
</project>

问题

  在使用spring data jpa+QueryDSL开发过程中发现,当多表查询返回结果不使用DTO进行封装的话只能使用Projections.map进行封装,保证返回的结果集是key-value形式,而不是object[],但是在实际中发现返回的结果集里key的形式是QEntity的名字.字段名(如:mem.birthDate)。
  查询部分的代码如下:

       public Map<String, Object> findAllMember(Pageable pageable) {QMemberEntity qMember = new QMemberEntity("mem");JPAQuery query = jpaQueryFactory.select(Projections.map(qMember.memberGuid.as("memberGuid"),qMember.birthDate,qMember.guardianSShip,qMember.realName,qMember.nickName)).from(qMember).offset(pageable.getOffset()).limit(pageable.getPageSize());Map<String, Object> result = new HashMap<>(5);result.put("total", query.fetchCount());result.put("data",query.fetch());result.put("pageSize", pageable.getPageSize());result.put("pageNumber", pageable.getPageNumber());result.put("offset", pageable.getOffset());return result;}

  返回的执行结果如下:

{"code": "200","msg": "ok","data": {"pageSize": 10,"total": 196,"pageNumber": 0,"data": [{"mem.birthDate": "2014-03-13","mem.guardianSShip": 12,"mem.realName": "asddd","mem.nickName": "","mem.memberGuid as memberGuid": "c0091fe1-6faa-473d-9428-cf9f02fda899"},{"mem.birthDate": "2014-08-06","mem.guardianSShip": 12,"mem.realName": "asddd1","mem.nickName": "","mem.memberGuid as memberGuid": "35406c3e-da06-426c-998b-c41e59c38941"},{"mem.birthDate": "2019-05-01","mem.guardianSShip": 12,"mem.realName": "asddd3","mem.nickName": "","mem.memberGuid as memberGuid": "94d04f7c-752a-4451-babf-a53c736de9da"},{"mem.birthDate": "2009-05-08","mem.guardianSShip": 13,"mem.realName": "测试3","mem.nickName": "","mem.memberGuid as memberGuid": "926f53d1-aec8-4172-9c74-c4bbe7e0f92c"},{"mem.birthDate": "2015-12-23","mem.guardianSShip": 13,"mem.realName": "asddd4","mem.nickName": "","mem.memberGuid as memberGuid": "31c5edc3-d1c6-4d33-a9ba-684fa77a7051"},{"mem.birthDate": "2014-10-11","mem.guardianSShip": 12,"mem.realName": "asddd5","mem.nickName": "","mem.memberGuid as memberGuid": "4cf95517-5ff0-4c1a-98a4-019f2ebac1af"},{"mem.birthDate": "2011-12-31","mem.guardianSShip": 13,"mem.realName": "asddd6","mem.nickName": "","mem.memberGuid as memberGuid": "f9ef258e-05f7-434b-9a35-928004bd129d"},{"mem.birthDate": "2016-05-25","mem.guardianSShip": 0,"mem.realName": "测试2","mem.nickName": "","mem.memberGuid as memberGuid": "7c5ac178-9903-4c18-88d2-80310d77839d"},{"mem.birthDate": "2019-05-08","mem.guardianSShip": 0,"mem.realName": "测试","mem.nickName": "","mem.memberGuid as memberGuid": "396ff1da-62a3-42d3-b8da-7b69a652ed4f"},{"mem.birthDate": "2014-01-03","mem.guardianSShip": 13,"mem.realName": "asddd7","mem.nickName": "","mem.memberGuid as memberGuid": "b1f300fb-06f5-461b-a8c6-e83f2fb9243c"}],"offset": 0}
}

分析

  1. 在网上查找好多资料发现除了使用DTO进行封装外只有一种方式,是在返回结果后再进行一次处理(代码如下)。这种方式虽然解决了上述的问题但是在返回的结果中包括的字段很多的情况写起来费时费力外还很容易出错。
public Map<String, Object> findAllMember(Pageable pageable) {QMemberEntity qMember = new QMemberEntity("mem");JPAQuery query = jpaQueryFactory.select(Projections.map(qMember.memberGuid.as("memberGuid"),qMember.birthDate,qMember.guardianSShip,qMember.realName,qMember.nickName)).from(qMember).offset(pageable.getOffset()).limit(pageable.getPageSize());List<Tuple> rows = query.fetch();Map<String, Object> result = new HashMap<>(5);result.put("total", query.fetchCount());result.put("data",rows.stream().map(row->{Map map = new HashMap(row.size());map.put("memberGuid",row.get(qMember.memberGuid));map.put("birthDate",row.get(qMember.birthDate));map.put("guardianSShip",row.get(qMember.guardianSShip));map.put("realName",row.get(qMember.realName));map.put("nickName",row.get(qMember.nickName));return map;}));result.put("pageSize", pageable.getPageSize());result.put("pageNumber", pageable.getPageNumber());result.put("offset", pageable.getOffset());return result;}
  1. 没有找到通过配置方式解决上述问题的方法,可能不够细心吧。
  2. 没有办法只能看源码了,只过源码分析Projections.map是一个静态方法,在这个方法里new了一个QMap对象,源码如下。
 /*** Create a Map typed projection for the given expressions** <p>Example</p>* <pre>{@code* Map<Expression<?>, ?> map = query.select(*      Projections.map(user.firstName, user.lastName));* }</pre>** @param exprs arguments for the projection* @return factory expression*/public static QMap map(Expression<?>... exprs) {return new QMap(exprs);}
  1. 对源码进行单步调试(没有办法英文太烂看不懂官方文档),发现每次处理一条记录时都会调用QMap中的newInstance(Object… args)方法,通过源码可以看到在方法中有生成Map对象,方法中有两个比较关键的,一个是方法的参数args,这个对象是查询结果object[]对象,另一个是类属性args,这个对象是结果集字段的元数据对象列表。源码如下,只保留了QMap类的关键代码。
public class QMap extends FactoryExpressionBase<Map<Expression<?>,?>> {private static final long serialVersionUID = -7545994090073480810L;private final ImmutableList<Expression<?>> args;/*** Create a new QMap instance** @param args*/@SuppressWarnings("unchecked")protected QMap(Expression<?>... args) {super((Class) Map.class);this.args = ImmutableList.copyOf(args);}@Override@Nullablepublic Map<Expression<?>, ?> newInstance(Object... args) {Map<Expression<?>, Object> map = Maps.newHashMap();for (int i = 0; i < args.length; i++) {map.put(this.args.get(i), args[i]);}return map;}
}
  1. 通过调试源码发现调用newInstance方法是在调用JPAQuery.fetch()方法后被调用的,又进行跟踪发现序列化时生成的key就是字段元数据对象的toString值,所以判断重写newInstance方法可以定制返回结果集的数据。
  2. 分析QMap类的newInstance方法返回的是Map<Expression<?>, ?>,所以不能通过继承QMap重写newInstance,查看一下QMap父类FactoryExpressionBase类的源码(关键代码如下),发现newInstance方法的返回值是一个泛型,所以可以通过继承FactoryExpressionBase类重写newInstance方法实现定制返回结果集的数据。
/*** Common superclass for {@link FactoryExpression} implementations** @param <T>*/
public abstract class FactoryExpressionBase<T> extends ExpressionBase<T> implements FactoryExpression<T> {
private static class FactoryExpressionWrapper<T> extends ExpressionBase<T> implements FactoryExpression<T> {private final FactoryExpression<T> expr;public FactoryExpressionWrapper(FactoryExpression<T> expr) {super(expr.getType());this.expr = expr;}@Overridepublic List<Expression<?>> getArgs() {return expr.getArgs();}@Nullable@Overridepublic T newInstance(Object... args) {if (args != null) {for (Object arg : args) {if (arg != null) {return expr.newInstance(args);}}}return null;}
}

解决

  通过上述的分析和代码调试的结果,参考QMap源码自定义了一个继承FactoryExpressionBase类的返回结果集类型的QueryResultMap类,经过测试满足我的需求,问题解决。源码如下。

package rm.lesi.mms;import com.google.common.collect.ImmutableList;
import com.querydsl.core.types.Expression;
import com.querydsl.core.types.FactoryExpressionBase;
import com.querydsl.core.types.Visitor;import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;/*** @author * @version 1.0.0* @date 2020/5/26 3:32 下午*/
public class QueryResultMap extends FactoryExpressionBase<Map<String,?>> {private final ImmutableList<Expression<?>> args;public QueryResultMap(Expression<?>... args) {super((Class) Map.class);this.args = ImmutableList.copyOf(args);}@Overridepublic List<Expression<?>> getArgs() {return args;}@Override@Nullablepublic Map<String,?> newInstance(Object... args) {Map<String, Object> map = new HashMap<>(args.length);for (int i = 0; i < args.length; i++) {String key = this.getArgs().get(i).toString();if(key.contains(" as ")){key = key.split(" as ")[1];}else{key = key.split("\\.")[1];}map.put(key, args[i]);}return map;}@Nullable@Overridepublic <R, C> R accept(Visitor<R, C> v, @Nullable C context) {return v.visit(this, context);}
}

  调用的时候在jpaQueryFactory.select里new QueryResultMap(),代码如下:

    public Map<String, Object> findAllMember(Pageable pageable) {QMemberEntity qMember = new QMemberEntity("mem");JPAQuery query = jpaQueryFactory.select(new QueryResultMap(qMember.memberGuid.as("memberGuid"),qMember.birthDate,qMember.guardianSShip,qMember.realName,qMember.nickName)).from(qMember).offset(pageable.getOffset()).limit(pageable.getPageSize());Map<String, Object> result = new HashMap<>(5);result.put("total", query.fetchCount());result.put("data",query.fetch());result.put("pageSize", pageable.getPageSize());result.put("pageNumber", pageable.getPageNumber());result.put("offset", pageable.getOffset());return result;}

  返回的执行结果如下:

  "code": "200","msg": "ok","data": {"pageSize": 10,"total": 196,"pageNumber": 0,"data": [{"realName": "asddd","memberGuid": "c0091fe1-6faa-473d-9428-cf9f02fda899","guardianSShip": 12,"birthDate": "2014-03-13","nickName": ""},{"realName": "asddd1","memberGuid": "35406c3e-da06-426c-998b-c41e59c38941","guardianSShip": 12,"birthDate": "2014-08-06","nickName": ""},{"realName": "测试3","memberGuid": "94d04f7c-752a-4451-babf-a53c736de9da","guardianSShip": 12,"birthDate": "2019-05-01","nickName": ""},{"realName": "测试3","memberGuid": "926f53d1-aec8-4172-9c74-c4bbe7e0f92c","guardianSShip": 13,"birthDate": "2009-05-08","nickName": ""},{"realName": "asddd4","memberGuid": "31c5edc3-d1c6-4d33-a9ba-684fa77a7051","guardianSShip": 13,"birthDate": "2015-12-23","nickName": ""},{"realName": "asddd5","memberGuid": "4cf95517-5ff0-4c1a-98a4-019f2ebac1af","guardianSShip": 12,"birthDate": "2014-10-11","nickName": ""},{"realName": "asddd6","memberGuid": "f9ef258e-05f7-434b-9a35-928004bd129d","guardianSShip": 13,"birthDate": "2011-12-31","nickName": ""},{"realName": "测试2","memberGuid": "7c5ac178-9903-4c18-88d2-80310d77839d","guardianSShip": 0,"birthDate": "2016-05-25","nickName": ""},{"realName": "测试","memberGuid": "396ff1da-62a3-42d3-b8da-7b69a652ed4f","guardianSShip": 0,"birthDate": "2019-05-08","nickName": ""},{"realName": "asddd7","memberGuid": "b1f300fb-06f5-461b-a8c6-e83f2fb9243c","guardianSShip": 13,"birthDate": "2014-01-03","nickName": ""}],"offset": 0}
}

结论

  在spring data jpa +QueryDSL开发框架上进行开发使用起来还是很方便的,但是对应的文档还是比较少,在解决上述问题的过程中发现QueryDSL定制返回类型和结果集处理还是比较灵活。

这篇关于QueryDSL 自定义返回结果集类型的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/708520

相关文章

Vite 打包目录结构自定义配置小结

《Vite打包目录结构自定义配置小结》在Vite工程开发中,默认打包后的dist目录资源常集中在asset目录下,不利于资源管理,本文基于Rollup配置原理,本文就来介绍一下通过Vite配置自定义... 目录一、实现原理二、具体配置步骤1. 基础配置文件2. 配置说明(1)js 资源分离(2)非 JS 资

Python中Json和其他类型相互转换的实现示例

《Python中Json和其他类型相互转换的实现示例》本文介绍了在Python中使用json模块实现json数据与dict、object之间的高效转换,包括loads(),load(),dumps()... 项目中经常会用到json格式转为object对象、dict字典格式等。在此做个记录,方便后续用到该方

python中的显式声明类型参数使用方式

《python中的显式声明类型参数使用方式》文章探讨了Python3.10+版本中类型注解的使用,指出FastAPI官方示例强调显式声明参数类型,通过|操作符替代Union/Optional,可提升代... 目录背景python函数显式声明的类型汇总基本类型集合类型Optional and Union(py

MySQL中查询和展示LONGBLOB类型数据的技巧总结

《MySQL中查询和展示LONGBLOB类型数据的技巧总结》在MySQL中LONGBLOB是一种二进制大对象(BLOB)数据类型,用于存储大量的二进制数据,:本文主要介绍MySQL中查询和展示LO... 目录前言1. 查询 LONGBLOB 数据的大小2. 查询并展示 LONGBLOB 数据2.1 转换为十

聊聊springboot中如何自定义消息转换器

《聊聊springboot中如何自定义消息转换器》SpringBoot通过HttpMessageConverter处理HTTP数据转换,支持多种媒体类型,接下来通过本文给大家介绍springboot中... 目录核心接口springboot默认提供的转换器如何自定义消息转换器Spring Boot 中的消息

MyBatis的xml中字符串类型判空与非字符串类型判空处理方式(最新整理)

《MyBatis的xml中字符串类型判空与非字符串类型判空处理方式(最新整理)》本文给大家介绍MyBatis的xml中字符串类型判空与非字符串类型判空处理方式,本文给大家介绍的非常详细,对大家的学习或... 目录完整 Hutool 写法版本对比优化为什么status变成Long?为什么 price 没事?怎

Python自定义异常的全面指南(入门到实践)

《Python自定义异常的全面指南(入门到实践)》想象你正在开发一个银行系统,用户转账时余额不足,如果直接抛出ValueError,调用方很难区分是金额格式错误还是余额不足,这正是Python自定义异... 目录引言:为什么需要自定义异常一、异常基础:先搞懂python的异常体系1.1 异常是什么?1.2

C#之枚举类型与随机数详解

《C#之枚举类型与随机数详解》文章讲解了枚举类型的定义与使用方法,包括在main外部声明枚举,用于表示游戏状态和周几状态,枚举值默认从0开始递增,也可手动设置初始值以生成随机数... 目录枚举类型1.定义枚举类型(main外)2.使用生成随机数总结枚举类型1.定义枚举类型(main外)enum 类型名字

Python lambda函数(匿名函数)、参数类型与递归全解析

《Pythonlambda函数(匿名函数)、参数类型与递归全解析》本文详解Python中lambda匿名函数、灵活参数类型和递归函数三大进阶特性,分别介绍其定义、应用场景及注意事项,助力编写简洁高效... 目录一、lambda 匿名函数:简洁的单行函数1. lambda 的定义与基本用法2. lambda

Linux中的自定义协议+序列反序列化用法

《Linux中的自定义协议+序列反序列化用法》文章探讨网络程序在应用层的实现,涉及TCP协议的数据传输机制、结构化数据的序列化与反序列化方法,以及通过JSON和自定义协议构建网络计算器的思路,强调分层... 目录一,再次理解协议二,序列化和反序列化三,实现网络计算器3.1 日志文件3.2Socket.hpp