JPA关联配置与Querydsl查询问题:获取指定用户视频观看进度
问题分析与解决方案
核心问题排查方向
你的问题大概率是实体关联缺失和Querydsl关联查询写法错误共同导致的,以下是具体分析和修复步骤:
一、实体配置的缺陷
目前的实体结构存在两个关键缺失:
- ProgramVideo与Program的双向关联缺失:虽然Program可能配置了
@OneToMany指向ProgramVideo,但ProgramVideo没有@ManyToOne关联回Program,这会导致JPA/Querydsl在关联查询时无法正确识别两者的父子关系,后续关联VideoRecordHistory时容易出现逻辑漏洞。 - VideoRecordHistory与ProgramVideo的关联缺失:你只在VideoRecordHistory中存储了
program_video_id字段,但没有配置@ManyToOne关联到ProgramVideo实体,这会让Querydsl无法自动生成关联查询的SQL,只能手动拼接条件,极易出错。
补全实体关联的代码示例
- 在
ProgramVideo类中添加关联:
@ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "program_id") // 对应数据库中ProgramVideo表的program_id字段 private Program program;
- 在
VideoRecordHistory类中添加关联:
@ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "program_video_id") // 对应数据库中该表的program_video_id字段 private ProgramVideo programVideo; @Column(name = "member_id") private Long memberId; // 其他字段:比如progress(观看进度)、watchTime(观看时长)等
二、Querydsl的正确用法
补全实体关联后,需要用**左连接(left join)**来关联VideoRecordHistory,同时将memberId的过滤条件放在on子句中(避免过滤掉无观看记录的ProgramVideo)。
示例Querydsl代码(返回Tuple或自定义DTO)
1. 返回Tuple(直接获取实体和进度)
QProgram program = QProgram.program; QProgramVideo programVideo = QProgramVideo.programVideo; QVideoRecordHistory videoRecordHistory = QVideoRecordHistory.videoRecordHistory; // 目标ProgramId和MemberId Long targetProgramId = 1L; Long targetMemberId = 100L; List<Tuple> result = queryFactory.select(programVideo, videoRecordHistory.progress) .from(programVideo) // 左连接VideoRecordHistory,同时指定关联的programVideo和目标memberId .leftJoin(videoRecordHistory) .on(programVideo.id.eq(videoRecordHistory.programVideo.id) .and(videoRecordHistory.memberId.eq(targetMemberId))) // 过滤指定Program下的视频 .where(programVideo.program.id.eq(targetProgramId)) .fetch();
2. 返回自定义DTO(更友好的业务数据结构)
先定义一个DTO类:
public class ProgramVideoWithProgress { private ProgramVideo programVideo; private Integer progress; // 观看进度,无记录时为null public ProgramVideoWithProgress(ProgramVideo programVideo, Integer progress) { this.programVideo = programVideo; this.progress = progress; } // getter/setter }
然后用Querydsl构造DTO:
List<ProgramVideoWithProgress> result = queryFactory.select( Projections.constructor(ProgramVideoWithProgress.class, programVideo, videoRecordHistory.progress)) .from(programVideo) .leftJoin(videoRecordHistory) .on(programVideo.id.eq(videoRecordHistory.programVideo.id) .and(videoRecordHistory.memberId.eq(targetMemberId))) .where(programVideo.program.id.eq(targetProgramId)) .fetch();
三、为什么之前的查询会返回所有用户记录?
如果没有补全实体关联,你可能是直接对三张表做了笛卡尔积查询,或者join条件只关联了program_video_id但没在正确的位置过滤memberId:
- 若把
memberId放在where子句中,且用的是inner join,会过滤掉无观看记录的视频,但如果join条件错误,依然会匹配到所有用户的记录; - 若用left join但没把
memberId放在on子句中,where条件会过滤掉关联后memberId不匹配的行,但如果实体关联缺失,Querydsl生成的SQL可能没有正确关联表,导致过滤失效。
内容的提问来源于stack exchange,提问作者Inseok Chang
相关产品推荐
相关产品推荐

