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

相关文章

SpringBoot全局异常拦截与自定义错误页面实现过程解读

《SpringBoot全局异常拦截与自定义错误页面实现过程解读》本文介绍了SpringBoot中全局异常拦截与自定义错误页面的实现方法,包括异常的分类、SpringBoot默认异常处理机制、全局异常拦... 目录一、引言二、Spring Boot异常处理基础2.1 异常的分类2.2 Spring Boot默

自定义注解SpringBoot防重复提交AOP方法详解

《自定义注解SpringBoot防重复提交AOP方法详解》该文章描述了一个防止重复提交的流程,通过HttpServletRequest对象获取请求信息,生成唯一标识,使用Redis分布式锁判断请求是否... 目录防重复提交流程引入依赖properties配置自定义注解切面Redis工具类controller

Springboot3统一返回类设计全过程(从问题到实现)

《Springboot3统一返回类设计全过程(从问题到实现)》文章介绍了如何在SpringBoot3中设计一个统一返回类,以实现前后端接口返回格式的一致性,该类包含状态码、描述信息、业务数据和时间戳,... 目录Spring Boot 3 统一返回类设计:从问题到实现一、核心需求:统一返回类要解决什么问题?

MyBatis中的两种参数传递类型详解(示例代码)

《MyBatis中的两种参数传递类型详解(示例代码)》文章介绍了MyBatis中传递多个参数的两种方式,使用Map和使用@Param注解或封装POJO,Map方式适用于动态、不固定的参数,但可读性和安... 目录✅ android方式一:使用Map<String, Object>✅ 方式二:使用@Param

SpringBoot返回文件让前端下载的几种方式

《SpringBoot返回文件让前端下载的几种方式》文章介绍了开发中文件下载的两种常见解决方案,并详细描述了通过后端进行下载的原理和步骤,包括一次性读取到内存和分块写入响应输出流两种方法,此外,还提供... 目录01 背景02 一次性读取到内存,通过响应输出流输出到前端02 将文件流通过循环写入到响应输出流

C# WebAPI的几种返回类型方式

《C#WebAPI的几种返回类型方式》本文主要介绍了C#WebAPI的几种返回类型方式,包括直接返回指定类型、返回IActionResult实例和返回ActionResult,文中通过示例代码介绍的... 目录创建 Controller 和 Model 类在 Action 中返回 指定类型在 Action

kafka自定义分区器使用详解

《kafka自定义分区器使用详解》本文介绍了如何根据企业需求自定义Kafka分区器,只需实现Partitioner接口并重写partition()方法,示例中,包含cuihaida的数据发送到0号分区... 目录kafka自定义分区器假设现在有一个需求使用分区器的方法总结kafka自定义分区器根据企业需求

python中的鸭子类型详解

《python中的鸭子类型详解》鸭子类型是Python动态类型系统的灵魂,它通过强调“行为”而非“类型”,赋予了代码极大的灵活性和表现力,本文给大家详细介绍python中的鸭子类型,感兴趣的朋友一起看... 目录1. 核心思想:什么是鸭子类型?2. 与“传统”静态类型语言的对比3. python 中无处不在

Java枚举类型深度详解

《Java枚举类型深度详解》Java的枚举类型(enum)是一种强大的工具,它不仅可以让你的代码更简洁、可读,而且通过类型安全、常量集合、方法重写和接口实现等特性,使得枚举在很多场景下都非常有用,本文... 目录前言1. enum关键字的使用:定义枚举类型什么是枚举类型?如何定义枚举类型?使用枚举类型:2.

C#中通过Response.Headers设置自定义参数的代码示例

《C#中通过Response.Headers设置自定义参数的代码示例》:本文主要介绍C#中通过Response.Headers设置自定义响应头的方法,涵盖基础添加、安全校验、生产实践及调试技巧,强... 目录一、基础设置方法1. 直接添加自定义头2. 批量设置模式二、高级配置技巧1. 安全校验机制2. 类型