You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JPA关联配置与Querydsl查询问题:获取指定用户视频观看进度

问题分析与解决方案

核心问题排查方向

你的问题大概率是实体关联缺失和Querydsl关联查询写法错误共同导致的,以下是具体分析和修复步骤:

一、实体配置的缺陷

目前的实体结构存在两个关键缺失:

  1. ProgramVideo与Program的双向关联缺失:虽然Program可能配置了@OneToMany指向ProgramVideo,但ProgramVideo没有@ManyToOne关联回Program,这会导致JPA/Querydsl在关联查询时无法正确识别两者的父子关系,后续关联VideoRecordHistory时容易出现逻辑漏洞。
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 20:13:19