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

相关文章

Vue3组件中getCurrentInstance()获取App实例,但是返回null的解决方案

《Vue3组件中getCurrentInstance()获取App实例,但是返回null的解决方案》:本文主要介绍Vue3组件中getCurrentInstance()获取App实例,但是返回nu... 目录vue3组件中getCurrentInstajavascriptnce()获取App实例,但是返回n

前端下载文件时如何后端返回的文件流一些常见方法

《前端下载文件时如何后端返回的文件流一些常见方法》:本文主要介绍前端下载文件时如何后端返回的文件流一些常见方法,包括使用Blob和URL.createObjectURL创建下载链接,以及处理带有C... 目录1. 使用 Blob 和 URL.createObjectURL 创建下载链接例子:使用 Blob

MySQL 中查询 VARCHAR 类型 JSON 数据的问题记录

《MySQL中查询VARCHAR类型JSON数据的问题记录》在数据库设计中,有时我们会将JSON数据存储在VARCHAR或TEXT类型字段中,本文将详细介绍如何在MySQL中有效查询存储为V... 目录一、问题背景二、mysql jsON 函数2.1 常用 JSON 函数三、查询示例3.1 基本查询3.2

Python获取C++中返回的char*字段的两种思路

《Python获取C++中返回的char*字段的两种思路》有时候需要获取C++函数中返回来的不定长的char*字符串,本文小编为大家找到了两种解决问题的思路,感兴趣的小伙伴可以跟随小编一起学习一下... 有时候需要获取C++函数中返回来的不定长的char*字符串,目前我找到两种解决问题的思路,具体实现如下:

Pydantic中Optional 和Union类型的使用

《Pydantic中Optional和Union类型的使用》本文主要介绍了Pydantic中Optional和Union类型的使用,这两者在处理可选字段和多类型字段时尤为重要,文中通过示例代码介绍的... 目录简介Optional 类型Union 类型Optional 和 Union 的组合总结简介Pyd

Oracle数据库常见字段类型大全以及超详细解析

《Oracle数据库常见字段类型大全以及超详细解析》在Oracle数据库中查询特定表的字段个数通常需要使用SQL语句来完成,:本文主要介绍Oracle数据库常见字段类型大全以及超详细解析,文中通过... 目录前言一、字符类型(Character)1、CHAR:定长字符数据类型2、VARCHAR2:变长字符数

Spring Boot 配置文件之类型、加载顺序与最佳实践记录

《SpringBoot配置文件之类型、加载顺序与最佳实践记录》SpringBoot的配置文件是灵活且强大的工具,通过合理的配置管理,可以让应用开发和部署更加高效,无论是简单的属性配置,还是复杂... 目录Spring Boot 配置文件详解一、Spring Boot 配置文件类型1.1 applicatio

使用Sentinel自定义返回和实现区分来源方式

《使用Sentinel自定义返回和实现区分来源方式》:本文主要介绍使用Sentinel自定义返回和实现区分来源方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Sentinel自定义返回和实现区分来源1. 自定义错误返回2. 实现区分来源总结Sentinel自定

如何自定义Nginx JSON日志格式配置

《如何自定义NginxJSON日志格式配置》Nginx作为最流行的Web服务器之一,其灵活的日志配置能力允许我们根据需求定制日志格式,本文将详细介绍如何配置Nginx以JSON格式记录访问日志,这种... 目录前言为什么选择jsON格式日志?配置步骤详解1. 安装Nginx服务2. 自定义JSON日志格式各

Android自定义Scrollbar的两种实现方式

《Android自定义Scrollbar的两种实现方式》本文介绍两种实现自定义滚动条的方法,分别通过ItemDecoration方案和独立View方案实现滚动条定制化,文章通过代码示例讲解的非常详细,... 目录方案一:ItemDecoration实现(推荐用于RecyclerView)实现原理完整代码实现