jpqlQueryFactory.selectFrom详解

news/2025/11/25 16:49:51/文章来源:https://www.cnblogs.com/hanease/p/19269055
jpqlQueryFactory.selectFrom 查询多个表的字段如何处理
在 JPA 的 JPQLQueryFactory(QueryDSL 整合 JPA 时的核心类)中查询多个表的字段,核心是 先通过关联关系(Join)关联多表,再通过 select() 指定需要查询的字段,最终通过 fetch() 或 fetchOne() 获取结果。根据需求不同,可分为「查询指定字段(投影查询)」「查询关联实体」「复杂关联查询」三种场景,以下是详细实操方案(基于 QueryDSL 5.x + JPA 2.2+):

一、前置准备(必要前提)

1. 依赖与基础配置

确保项目引入 QueryDSL 相关依赖(以 Maven 为例):
xml
 
 
<!-- QueryDSL 核心 -->
<dependency><groupId>com.querydsl</groupId><artifactId>querydsl-jpa</artifactId><version>5.0.0</version>
</dependency>
<dependency><groupId>com.querydsl</groupId><artifactId>querydsl-apt</artifactId><version>5.0.0</version><scope>provided</scope>
</dependency>
 
并通过 APT 插件生成 QueryDSL 对应的查询实体(如 QUserQOrder,前缀为 Q,与 JPA 实体类对应)。

2. 示例实体与关联关系

假设存在两个关联表:User(用户表)和 Order(订单表),关联关系为「一对多」(一个用户可拥有多个订单):
java
 
运行
 
 
 
 
// User 实体(一的一方)
@Entity
@Table(name = "user")
public class User {@Idprivate Long id;private String username;private Integer age;// 一对多关联 Order@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)private List<Order> orders;// getter/setter
}// Order 实体(多的一方)
@Entity
@Table(name = "t_order") // 避免与数据库关键字冲突
public class Order {@Idprivate Long id;private String orderNo;private BigDecimal amount;// 多对一关联 User(外键)@ManyToOne(fetch = FetchType.LAZY)@JoinColumn(name = "user_id")private User user;// getter/setter
}
 
生成的 QueryDSL 查询实体:QUser(对应 User)、QOrder(对应 Order)。

二、核心场景:多表关联 + 投影查询(指定字段)

最常用场景:关联多表后,只查询需要的字段(而非整个实体),避免冗余数据。QueryDSL 支持两种投影方式:「Tuple 投影」(灵活,无需自定义类)和「DTO 投影」(类型安全,推荐)。

场景 1:Tuple 投影(无需自定义类,快速查询)

适合临时查询少量字段,结果存储在 Tuple 中,通过字段名或索引获取值。

代码示例(查询用户名称 + 订单号 + 订单金额):

java
 
运行
 
 
 
 
import com.querydsl.core.Tuple;
import com.querydsl.jpa.JPQLQueryFactory;
import org.springframework.stereotype.Service;
import javax.persistence.EntityManager;
import java.util.List;
import java.util.stream.Collectors;@Service
public class UserOrderService {private final JPQLQueryFactory jpqlQueryFactory;private final QUser qUser = QUser.user; // QueryDSL 生成的查询实体private final QOrder qOrder = QOrder.order;// 注入 EntityManager,初始化 JPQLQueryFactorypublic UserOrderService(EntityManager entityManager) {this.jpqlQueryFactory = new JPQLQueryFactory(entityManager);}// 多表查询:用户名称 + 订单号 + 订单金额(Tuple 投影)public List<Tuple> queryUserOrderTuple() {return jpqlQueryFactory.select(qUser.username, // User 表字段qOrder.orderNo, // Order 表字段qOrder.amount   // Order 表字段).from(qUser) // 主表:User.join(qUser.orders, qOrder) // 关联 Order(一对多关联,join 等价于 inner join).where(qUser.age.gt(18)) // 筛选条件:用户年龄 > 18.fetch(); // 执行查询,返回 Tuple 列表}// 解析 Tuple 结果(转为业务数据)public List<UserOrderVO> parseTupleResult(List<Tuple> tuples) {return tuples.stream().map(tuple -> {UserOrderVO vo = new UserOrderVO();// 方式 1:通过查询字段获取(类型安全,推荐)vo.setUsername(tuple.get(qUser.username));vo.setOrderNo(tuple.get(qOrder.orderNo));vo.setAmount(tuple.get(qOrder.amount));// 方式 2:通过索引获取(需与 select 字段顺序一致)// vo.setUsername(tuple.get(0, String.class));// vo.setOrderNo(tuple.get(1, String.class));// vo.setAmount(tuple.get(2, BigDecimal.class));return vo;}).collect(Collectors.toList());}
}// 业务 VO(仅用于接收结果)
class UserOrderVO {private String username;private String orderNo;private BigDecimal amount;// getter/setter
}
 

关键说明:

  • join(qUser.orders, qOrder):通过 User 的 orders 关联属性(一对多)关联 OrderqOrder 是关联表的查询实体别名;
  • select(...) 中可混合多个表的字段,顺序不限;
  • Tuple.get(字段) 类型安全,无需手动强转(推荐),Tuple.get(索引, 类型) 需确保顺序和类型正确。

场景 2:DTO 投影(类型安全,推荐生产使用)

适合查询字段较多、需要复用结果结构的场景,通过自定义 DTO 接收结果,避免 Tuple 索引 / 字段名写错的风险。QueryDSL 支持两种 DTO 投影方式:「构造函数投影」和「@QueryProjection 投影」。

方式 A:构造函数投影(无需修改 DTO,简单直接)

DTO 无需任何注解,通过 select(new QUserOrderDTO(...)) 直接传入查询字段。

代码示例:

java
 
运行
 
 
 
 
// 自定义 DTO(无注解,仅含构造函数)
class UserOrderDTO {private String username;private String orderNo;private BigDecimal amount;// 必须提供与查询字段顺序、类型一致的构造函数public UserOrderDTO(String username, String orderNo, BigDecimal amount) {this.username = username;this.orderNo = orderNo;this.amount = amount;}// getter
}// 查询代码(构造函数投影)
public List<UserOrderDTO> queryUserOrderDTO() {return jpqlQueryFactory.select(new QUserOrderDTO( // 直接通过 DTO 构造函数传入字段qUser.username,qOrder.orderNo,qOrder.amount)).from(qUser).join(qUser.orders, qOrder) // 内连接.where(qOrder.amount.gt(new BigDecimal("100"))) // 订单金额 > 100.fetch(); // 直接返回 DTO 列表,无需手动解析
}
 

方式 B:@QueryProjection 投影(需生成 QueryDSL DTO 实体,更高效)

DTO 加 @QueryProjection 注解,通过 APT 生成 QUserOrderDTO 查询实体,投影更高效(推荐字段较多时使用)。

代码示例:

java
 
运行
 
 
 
 
import com.querydsl.core.annotations.QueryProjection;// 带 @QueryProjection 的 DTO
class UserOrderDTO {private String username;private String orderNo;private BigDecimal amount;// 构造函数加 @QueryProjection 注解@QueryProjectionpublic UserOrderDTO(String username, String orderNo, BigDecimal amount) {this.username = username;this.orderNo = orderNo;this.amount = amount;}// getter
}
 
注意:添加注解后需重新编译项目,APT 会生成 QUserOrderDTO 实体(与 DTO 同名,前缀 Q)。

查询代码:

java
 
运行
 
 
 
 
// 注入生成的 QUserOrderDTO
private final QUserOrderDTO qUserOrderDTO = new QUserOrderDTO(null, null, null);public List<UserOrderDTO> queryUserOrderDTOByProjection() {return jpqlQueryFactory.select(qUserOrderDTO) // 直接使用 QueryDSL DTO 实体.from(qUser).leftJoin(qUser.orders, qOrder) // 左连接(即使无订单也会返回用户).on(qOrder.amount.gt(new BigDecimal("100"))) // 左连接条件用 on(内连接用 where).fetch();
}
 

两种 DTO 投影对比:

方式优点缺点适用场景
构造函数投影 无需修改 DTO、无需编译 字段顺序 / 类型错误编译不报错 字段少、临时查询
@QueryProjection 类型安全、查询高效 需加注解、重新编译生成 Q 类 字段多、复用 DTO 的场景

三、场景 3:查询关联实体(而非单个字段)

若需要查询整个关联实体(如查询用户及其所有订单),无需指定单个字段,直接 select 实体或关联实体即可,QueryDSL 会自动处理关联关系。

代码示例(查询用户及其关联的订单):

java
 
运行
 
 
 
 
// 查询用户实体 + 关联的订单实体(一对多)
public List<User> queryUserWithOrders() {return jpqlQueryFactory.selectFrom(qUser) //  selectFrom = select(qUser) + from(qUser).leftJoin(qUser.orders, qOrder) // 左连接订单表.fetchJoin() // 关键:fetchJoin 避免 N+1 问题(一次性加载关联实体).where(qUser.username.eq("张三")).fetch();
}
 

关键说明:

  • fetchJoin():必须添加!JPA 默认懒加载(FetchType.LAZY),若不加 fetchJoin,查询用户后访问 user.getOrders() 会触发额外 SQL(N+1 问题),fetchJoin 会一次性加载用户和关联的订单;
  • 若仅查询订单及其关联的用户,逻辑类似:
    java
     
    运行
     
     
     
     
    public List<Order> queryOrderWithUser() {return jpqlQueryFactory.selectFrom(qOrder).join(qOrder.user, qUser) // 订单关联用户(多对一).fetchJoin().where(qOrder.amount.gt(new BigDecimal("500"))).fetch();
    }
    
     
     

四、复杂场景:多表关联(3 表及以上)+ 条件筛选

若需关联 3 个及以上表(如 User → Order → OrderItem),逻辑与两表关联一致:先通过 join 依次关联,再指定查询字段或实体。

示例(3 表关联:User → Order → OrderItem):

假设新增 OrderItem 实体(订单明细,与 Order 为一对多关联):
java
 
运行
 
 
 
 
@Entity
@Table(name = "order_item")
public class OrderItem {@Idprivate Long id;private String productName;private Integer quantity;@ManyToOne(fetch = FetchType.LAZY)@JoinColumn(name = "order_id")private Order order;// getter/setter
}
 
生成查询实体 QOrderItem

查询代码(用户名称 + 订单号 + 商品名称 + 数量):

java
 
运行
 
 
 
 
private final QOrderItem qOrderItem = QOrderItem.orderItem;public List<UserOrderItemDTO> query3TableDTO() {return jpqlQueryFactory.select(new QUserOrderItemDTO( // 自定义 3 表字段 DTOqUser.username,qOrder.orderNo,qOrderItem.productName,qOrderItem.quantity)).from(qUser) // 主表:User.join(qUser.orders, qOrder) // 关联表 1:Order.join(qOrder.orderItems, qOrderItem) // 关联表 2:OrderItem.where(qUser.age.between(20, 30), // User 表条件qOrderItem.quantity.gt(1) // OrderItem 表条件).fetch();
}
 

五、关键注意事项(避坑指南)

1. 关联类型与 join 语法

  • inner join(默认 join):仅返回关联表有匹配数据的记录(如用户必须有订单才会被查询到);
  • left join:返回主表所有记录,关联表无匹配数据时为 null(如用户无订单也会返回);
  • 左连接的条件需用 on 而非 where:若用 where 会将左连接转为内连接(筛选条件过滤了 null 记录):
    java
     
    运行
     
     
     
     
    // 错误:左连接用 where 筛选关联表,等价于内连接
    .leftJoin(qUser.orders, qOrder)
    .where(qOrder.amount.gt(100))// 正确:左连接用 on 筛选关联表
    .leftJoin(qUser.orders, qOrder)
    .on(qOrder.amount.gt(100))
    
     
     

2. 避免 N+1 问题

关联查询实体时,必须用 fetchJoin() 一次性加载关联数据,否则会触发 N+1 问题(如查询 10 个用户,每个用户触发 1 次订单查询,共 11 次 SQL)。

3. 字段歧义处理

若多表存在同名字段(如 User 和 Order 都有 create_time),需通过查询实体指定表别名,避免歧义:
java
 
运行
 
 
 
 
.select(qUser.createTime.as("userCreateTime"), // 别名区分qOrder.createTime.as("orderCreateTime")
)
 

4. 权限与性能

  • 多表关联查询尽量避免 select *(投影查询指定必要字段),减少数据传输量;
  • 复杂查询可通过 fetchCount() 先统计数量,或添加分页(offset + limit):
    java
     
    运行
     
     
     
     
    // 分页查询(第 1 页,每页 10 条)
    return jpqlQueryFactory.select(qUserOrderDTO).from(qUser).join(qUser.orders, qOrder).offset(0) // 起始索引(从 0 开始).limit(10) // 每页条数.fetch();
    
     
     

六、总结

JPQLQueryFactory 多表查询的核心流程:
  1. 关联多表:通过 join/leftJoin + 实体关联属性(如 qUser.orders)关联目标表;
  2. 指定结果:
    • 需单个字段:用 Tuple 或 DTO 投影(推荐 DTO 类型安全);
    • 需整个实体:用 selectFrom + fetchJoin(避免 N+1);
  3. 执行查询:fetch()(返回列表)或 fetchOne()(返回单个结果)。
关键原则:关联用 join,投影用 DTO,加载关联用 fetchJoin,条件筛选注意 join 类型,即可高效、安全地实现多表查询。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/976211.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

智能打气泵pcba方案设计

打气泵主要使用在汽车车胎打气上面,通过马达的运转来工作。马达运转抽气时,连通器的阀门被大气的气压冲开,气体进入气筒,而向轮胎中打气时,阀门又被气筒内的气压关闭,气体就进入了轮胎中。今天我们就来聊一下关于…

使用 html2canvas + jsPDF 生成PDF 的简单示例(含文字下沉修复)

在前端项目中,尤其是后台管理系统、发票/报告导出、在线编辑器、前端页面截图导出等场景,将页面内容导出为 PDF 是非常常见的需求。现在使用 html2canvas + jsPDF 进行构建 hooks 作为示例。 一、为什么需要自定义封…

2025下半年江苏徐州箱式变压器,干式变压器,油浸式变压器,高低压成套设备,箱式变电站品牌实力厂家推荐指南:五大优质厂家深度解析

摘要 随着新能源建设和智能电网的快速发展,2025年下半年箱式变电站行业迎来新一轮增长期。本文基于行业调研和用户反馈,为您推荐五家值得关注的箱式变电站生产企业。榜单排名不分先后,旨在为需求企业提供参考选择,…

实用指南:(3)Kafka生产者分区策略、ISR、ACK、一致性语义

实用指南:(3)Kafka生产者分区策略、ISR、ACK、一致性语义2025-11-25 16:38 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !importa…

2025 经皮黄疸检测仪十大品牌推荐:经皮黄疸测试仪标杆厂家

新生儿黄疸作为临床最常见的新生儿病症之一,其精准筛查与动态监测直接关系到婴幼儿健康成长。2025 年,随着智慧医疗与分级诊疗政策的深度推进,经皮黄疸检测仪器市场迎来技术升级与场景细分的双重爆发,兼具高精度、…

ABC425 VP总结

比赛链接 Result请输入文本…… 我讨厌 BFS.jpg Solution ABC 不写,E 可能是我做过的最简单的 E 了。 D - Ulam-Warburton Automaton 我讨厌 BFS 存下每个节点是第几轮被遍历到的,转移时 check 一下是不是由同一轮的…

LCD驱动芯片抗干扰段码屏驱动控制器VK2C21A液晶驱动原厂 提供技术支持

VK2C21A是一个点阵式存储映射的LCD驱动器,可支持最大80点(20SEGx4COM)或者最大128点(16SEGx8COM)的LCD屏。单片机可通过I2C接口配置显示参数和读写显示数据,也可通过指令进入省电模式。其高抗干扰,低功耗的特性…

阳江一对一辅导机构推荐,2026年高性价比家教机构口碑排名公布!

阳江家长们是不是都在为孩子的课外补习操碎了心?江城区、阳东区、阳春市、阳西县、海陵区、高新区的小学、初中、高中家长,想找靠谱的一对一辅导机构,却被五花八门的选择绕得头晕——线下机构收费虚高,线上平台需下…

2025.11.24 NOI 模拟赛

神秘场。 \(0+100+70\) 不会做 T1 遗憾离场了。不过为啥 hxq 和 yjh 不会 T2。 排列(perm) 原题 首先考虑容斥。记 \(dp_x\) 表示选出 \(x\) 个位置不满足限制的方案数。如果我们已经知道了 \(dp_x\) 那么我们可以通过…

2025年耐腐蚀砂浆厂家权威推荐榜单:环氧砂浆/环氧修补砂浆/环氧树脂砂浆源头厂家精选

耐腐蚀砂浆作为特种建筑材料,其性能直接关系到工业建筑、海工工程等特殊环境的耐久性与安全性。以下将基于企业产能、产品性能、市场覆盖及服务能力等客观维度,为您梳理三家在耐腐蚀砂浆领域具有代表性的企业,为行业…

2025年哈尔滨及周边地区艺考培训机构综合评估:七台河、双鸭山、黑河、舞蹈艺考、服表艺考、美术艺考、音乐艺考、聚焦教学实力与地域服务覆盖深度解析

随着艺考竞争日益激烈,考生及家长在选择培训机构时面临多重考量——如何评估机构的教学专业性、课程体系与地域服务的匹配度,以及教学成果的可持续性,成为决策的关键。为帮助考生精准筛选适合的艺考辅导机构,本次评…

2025年水分分析仪选型‌生产厂家权威推荐榜单:‌水分监测解决方案‌/‌近红外水分检测‌/动态水分分析系统‌源头厂家精选

在工业过程控制与质量控制中,水分含量的精准测定直接关系到产品品质、生产安全与能源消耗。据行业报告显示,2025年全球水分分析仪市场规模预计将达到86亿元,其中近红外光谱技术与可调谐二极管激光分析技术成为增长最…

2025年东北艺考培训优质机构推荐:众艺艺考,黑龙江、哈尔滨、齐齐哈尔、大庆地区、多品类艺考辅导助力升学

随着教育多元化发展与艺术人才需求增长,艺考已成为众多学生实现升学梦想的重要途径。2025 年,黑龙江、吉林等东北地区艺考报考热度持续攀升,但市场上培训机构的教学质量、师资配置、课程体系等参差不齐,让学生和家…

储能电池的变换器进行了建模与仿真

对储能电池的变换器进行建模与仿真是一个复杂但非常重要的过程,涉及到电力电子、电池特性、控制理论等多个领域。 1. 储能电池变换器的基本组成 储能电池变换器通常包括以下几个主要部分:电池模型:描述电池的电化学…

Logo设计十大公司权威解析:从战略构建到视觉落地的卓越之选

在品牌竞争日益激烈的市场环境中,一个优秀的Logo不仅是企业形象的核心载体,更是品牌战略的视觉结晶。选择与品牌调性相匹配的设计伙伴,成为构建品牌资产的关键第一步。本文将为您深度解析十家各具特色的Logo设计公司…

生产环境误删了会员的数据,差点被开除。。。

前言 最近一位粉丝反馈说:一不小心误删了,修改了所有会员的过期时间,差点被开除。。。 确实,无论是开发、测试,还是DBA,都难免会涉及到数据库的操作,比如:创建某张表,添加某个字段、添加数据、更新数据、删除…

iOS 审核 5.1.1 深度解读,数据收集、权限合规与审核通过率提升的技术要点

本文深入解析 iOS 审核 5.1.1 的触发原因与解决方案,从权限说明、SDK 数据收集、隐私政策、数据传输与 WebView 行为等角度提出可操作的排查方法,并给出多技术栈下的合规实践。适用于原生、Hybrid 与跨平台应用的审核…

2025 年 11 月苏州短视频运营服务团队权威推荐榜:专业拍摄制作、矩阵代运营与高效拓客一体化解决方案

2025 年 11 月苏州短视频运营服务团队权威推荐榜:专业拍摄制作、矩阵代运营与高效拓客一体化解决方案 在数字经济蓬勃发展的今天,短视频已成为企业营销与品牌传播的重要渠道。苏州作为长三角地区的重要经济中心,其短…

Windows系统已经激活怎么改成未激活?

将已经激活的 Windows 系统改为未激活状态,主要通过以下方法实现。这通常用于测试、开发环境或需要重新激活系统的场景。以下是具体步骤和方法:方法 1:通过命令行清除产品密钥 Windows系统的激活状态与其产品密钥绑…

Miniconda+Vscode安装避雷

版本选择 https://mirrors.tuna.tsinghua.edu.cn/anaconda/miniconda/Miniconda3-latest-Windows-x86_64.exe 直接选择最新版本即可。 自动配置环境变量 一点要选择Just me才能自动配置环境变量!!Vscode不要用2025年…