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

如何在JPQL构造表达式中传递SELECT语句以实现正确DTO投影

JPA类投影查询返回重复DTO,如何实现单条文章对应唯一DTO?

我正在使用JPA基于类的投影构造ArticleDTO,该DTO包含title字段和isWrittenByMe字段——当传入的作者是文章的作者之一时,isWrittenByMe设为true,否则为false。

当前实现

我在JPA仓库中添加了自定义JPQL查询方法:

@Query("SELECT new com.example.learnjpql.dto.ArticleDTO(a.title, :author MEMBER OF a.authors) FROM Article a")
List<ArticleDTO> getAllArticles(Author author);

Hibernate生成的SQL查询:

Hibernate: select article0_.title as col_0_0_, ? in (authors1_.author_id) as col_1_0_ from article article0_ cross join article_authors2 authors1_ where article0_.id=authors1_.article_id

当前方法输出结果(出现重复DTO):

[ArticleDTO[title=Article 1, isAuthoredByMe=true], ArticleDTO[title=Article 1, isAuthoredByMe=false], ArticleDTO[title=Article 2, isAuthoredByMe=true], ArticleDTO[title=Article 3, isAuthoredByMe=false], ArticleDTO[title=Article 3, isAuthoredByMe=false], ArticleDTO[title=Article 4, isAuthoredByMe=false], ArticleDTO[title=Article 4, isAuthoredByMe=false], ArticleDTO[title=Article 5, isAuthoredByMe=false]]

期望结果

每个Article对应唯一的DTO,示例如下:

TitleisWrittenByMe
Article 1true
Article 2false
Article 3true
Article 4false
Article 5false

尝试过的方法

我曾尝试用SELECT语句替代:author MEMBER OF a.authors作为构造函数参数,希望为每条记录执行单独查询,但触发了“unexpected token”错误。


问题原因

当前JPQL中的MEMBER OF会触发Article与关联表article_authors2的交叉连接,一篇文章有多少个作者就会生成多少条DTO记录,其中只有匹配传入作者的条目为true,其余为false,最终导致结果重复。

解决方案

方案1:添加DISTINCT去重

在JPQL中加入DISTINCT关键字,确保每个Article只返回唯一的DTO:

@Query("SELECT DISTINCT new com.example.learnjpql.dto.ArticleDTO(a.title, :author MEMBER OF a.authors) FROM Article a")
List<ArticleDTO> getAllArticles(Author author);

方案2:使用子查询替代MEMBER OF

通过子查询判断作者是否属于文章的作者集合,避免交叉连接:

@Query("SELECT new com.example.learnjpql.dto.ArticleDTO(a.title, EXISTS (SELECT 1 FROM a.authors auth WHERE auth.id = :authorId)) FROM Article a")
List<ArticleDTO> getAllArticles(@Param("authorId") Long authorId);

注:改用作者ID作为参数能简化查询逻辑;若必须传入Author实体,可调整为auth = :author,你的代码中Author的equals/hashCode实现正确,不会有问题。

方案3:实体层添加@Formula(可选)

如果希望在实体层面直接获取该字段,可在Article实体中添加@Formula注解:

@Formula("(SELECT CASE WHEN EXISTS (SELECT 1 FROM article_authors2 aa WHERE aa.article_id = id AND aa.author_id = :authorId) THEN TRUE ELSE FALSE END)")
private boolean isWrittenByMe;

该方式需在查询时手动设置参数,灵活性稍差,适合固定场景。


相关实体与DTO代码

Author实体

@Entity
@NoArgsConstructor
@AllArgsConstructor
@Getter
public class Author {
    @Id
    private Long id;

    private String firstname;

    private String lastname;

    @ManyToMany(fetch = FetchType.EAGER, mappedBy = "authors")
    private Set<Article> articles;

    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (!(o instanceof Author author))
            return false;
        return getId().equals(author.getId()) && getFirstname().equals(author.getFirstname()) && getLastname().equals(author.getLastname());
    }

    @Override
    public int hashCode() {
        return Objects.hash(getId(), getFirstname(), getLastname());
    }

    public String toString() {
        return "Author(id=" + this.getId() + ", firstname=" + this.getFirstname() + ", lastname=" + this.getLastname();
    }
}

Article实体

@Entity
@NoArgsConstructor
@AllArgsConstructor
@Getter
@ToString
public class Article {

    @Id
    private Long id;

    private String title;

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(name = "article_authors2",
            joinColumns = { @JoinColumn(name = "article_id") },
            inverseJoinColumns = { @JoinColumn(name = "author_id") })
    private Set<Author> authors;

    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (!(o instanceof Article article))
            return false;
        return getId().equals(article.getId()) && getTitle().equals(article.getTitle());
    }

    @Override
    public int hashCode() {
        return Objects.hash(getId(), getTitle());
    }
}

ArticleDTO

public record ArticleDTO(
        String title,
        boolean isAuthoredByMe
) { }

内容的提问来源于stack exchange,提问作者Houssem Nasri

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 10:41:17