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

相关文章

Mysql 中的多表连接和连接类型详解

《Mysql中的多表连接和连接类型详解》这篇文章详细介绍了MySQL中的多表连接及其各种类型,包括内连接、左连接、右连接、全外连接、自连接和交叉连接,通过这些连接方式,可以将分散在不同表中的相关数据... 目录什么是多表连接?1. 内连接(INNER JOIN)2. 左连接(LEFT JOIN 或 LEFT

Redis的Hash类型及相关命令小结

《Redis的Hash类型及相关命令小结》edisHash是一种数据结构,用于存储字段和值的映射关系,本文就来介绍一下Redis的Hash类型及相关命令小结,具有一定的参考价值,感兴趣的可以了解一下... 目录HSETHGETHEXISTSHDELHKEYSHVALSHGETALLHMGETHLENHSET

Python中异常类型ValueError使用方法与场景

《Python中异常类型ValueError使用方法与场景》:本文主要介绍Python中的ValueError异常类型,它在处理不合适的值时抛出,并提供如何有效使用ValueError的建议,文中... 目录前言什么是 ValueError?什么时候会用到 ValueError?场景 1: 转换数据类型场景

SpringBoot 自定义消息转换器使用详解

《SpringBoot自定义消息转换器使用详解》本文详细介绍了SpringBoot消息转换器的知识,并通过案例操作演示了如何进行自定义消息转换器的定制开发和使用,感兴趣的朋友一起看看吧... 目录一、前言二、SpringBoot 内容协商介绍2.1 什么是内容协商2.2 内容协商机制深入理解2.2.1 内容

C# dynamic类型使用详解

《C#dynamic类型使用详解》C#中的dynamic类型允许在运行时确定对象的类型和成员,跳过编译时类型检查,适用于处理未知类型的对象或与动态语言互操作,dynamic支持动态成员解析、添加和删... 目录简介dynamic 的定义dynamic 的使用动态类型赋值访问成员动态方法调用dynamic 的

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

自定义类型:结构体(续)

目录 一. 结构体的内存对齐 1.1 为什么存在内存对齐? 1.2 修改默认对齐数 二. 结构体传参 三. 结构体实现位段 一. 结构体的内存对齐 在前面的文章里我们已经讲过一部分的内存对齐的知识,并举出了两个例子,我们再举出两个例子继续说明: struct S3{double a;int b;char c;};int mian(){printf("%zd\n",s

【编程底层思考】垃圾收集机制,GC算法,垃圾收集器类型概述

Java的垃圾收集(Garbage Collection,GC)机制是Java语言的一大特色,它负责自动管理内存的回收,释放不再使用的对象所占用的内存。以下是对Java垃圾收集机制的详细介绍: 一、垃圾收集机制概述: 对象存活判断:垃圾收集器定期检查堆内存中的对象,判断哪些对象是“垃圾”,即不再被任何引用链直接或间接引用的对象。内存回收:将判断为垃圾的对象占用的内存进行回收,以便重新使用。

flume系列之:查看flume系统日志、查看统计flume日志类型、查看flume日志

遍历指定目录下多个文件查找指定内容 服务器系统日志会记录flume相关日志 cat /var/log/messages |grep -i oom 查找系统日志中关于flume的指定日志 import osdef search_string_in_files(directory, search_string):count = 0