【问题标题】:JPA One to One mapping with inheritance: 2 Queries for what should be one具有继承的 JPA 一对一映射:2 查询应该是什么
【发布时间】:2023-11-07 11:31:01
【问题描述】:

Media 实体和MediaAnalysis 实体之间存在一对一的关系,其中Media 实体是一个抽象基类:

新闻报道实体

@Entity
@DiscriminatorValue("N")
public class NewsReport extends Media {

    @Column(name = "BODY", nullable = false)
    private String body;

    NewsReport(){}

    public NewsReport(String title, String link, String author, String body) {
        super(title, link, author);
        this.body= body;
    }

    public String getBody() {
        return body;
    }
}

媒体实体

@Entity
@Inheritance(
        strategy = InheritanceType.SINGLE_TABLE
)
@DiscriminatorColumn(name = "TYPE", length = 1, discriminatorType = DiscriminatorType.STRING)
public abstract class Media {

    @Id
    @GeneratedValue(strategy= GenerationType.AUTO)
    private Long id;

    @Column(name = "TITLE", nullable = false)
    private String title;

    @Column(name = "LINK", length = 500, nullable = false)
    private String link;

    @Column(name = "AUTHOR", length = 45, nullable = false)
    private String author;

    @OneToOne(mappedBy = "media")
    private MediaAnalysis analysis;

    Media(){}

    public Media(String title, String link, String author) {
        this.title = title;
        this.link = link;
        this.author = author;
    }

    // getters

    public Optional<MediaAnalysis> getAnalysis() {
        return Optional.ofNullable(analysis);
    }
}

媒体分析

@Entity
public class MediaAnalysis {

    @Id
    @GeneratedValue(strategy= GenerationType.AUTO)
    private Long id;

    @Column(name = "SUCCESS", nullable = false)
    private Boolean success;

    @OneToOne
    @JoinColumn(
            name = "MED_ID",
            nullable = false,
            foreignKey = @ForeignKey(name="MEA_MED_FK")
    )
    private Media media;

    @Column(name = "CONTENT", nullable = false)
    private String content;

    MediaAnalysis() { }

    public MediaAnalysis(Media media, Boolean success, String content) {
        this.media = media;
        this.success = success;
        this.content = content;
    }

    // getters

    public Media getMedia() {
        return media;
    }

    public String getContent() {
        return content;
    }
}

现在当我想使用我的AnalysisRepository.getByMedia(..a NewsReport...)

public interface AnalysisRepository extends JpaRepository<MediaAnalysis,Long> {

    @Query("SELECT a FROM MediaAnalysis a LEFT JOIN FETCH a.media WHERE a.media = ?1")
    Optional<MediaAnalysis> getByMedia(Media media);

}

例如,要通过NewsReport 查找MediaAnalysis,我希望hibernate 运行单个SELECT 查询,例如:

从媒体分析 m 中选择 m.* 其中 m.med_id = ?

但是,当我启用查询日志记录时,我看到了 2:

调试 ohSQL:92 - 选择 mediaanaly0_.id 作为 id1_0_0_,media1_.id 作为 id2_1_1_,mediaanaly0_.med_id 作为 med_id3_0_0_,mediaanaly0_.success 作为 success2_0_0_,media1_.author 作为 author3_1_1_,media1_.link 作为 link4_1_1_,media1_.title 作为title5_1_1_, media1_.body as body6_1_1_, media1_.type as type1_1_1_ from mea_media_analysis mediaanaly0_ left outer join med_media media1_ on mediaanaly0_.med_id=media1_.id where mediaanaly0_.med_id=?

TRACE o.h.t.d.s.BasicBinder:65 - 绑定参数 [1] 为 [BIGINT] - [1]

调试 ohSQL:92 - 选择 mediaanaly0_.id 作为 id1_0_1_,mediaanaly0_.med_id 作为 med_id3_0_1_,mediaanaly0_.success 作为 success2_0_1_,media1_.id 作为 id2_1_0_,media1_.author 作为 author3_1_0_,media1_.link 作为 link4_1_0_,media1_.title 作为title5_1_0_, media1_.body as body6_1_0_, media1_.type as type1_1_0_ from mea_media_analysis mediaanaly0_ inner join med_media media1_ on mediaanaly0_.med_id=media1_.id where mediaanaly0_.med_id=?

TRACE o.h.t.d.s.BasicBinder:65 - 绑定参数 [1] 为 [BIGINT] - [1]

似乎首先按预期选择了MediaAnalysis,但随后还有一个似乎不必要的查询。我可以告诉这两个查询之间的唯一区别是连接类型。我认为问题与Media 继承有关。

为什么会这样? + 我该怎么做才能确保这是一个查询?


进一步说明,如果我从存储库中删除 @Query,实际上会有 三个 查询!。

调试 ohSQL:92 - 选择 mediaanaly0_.id 作为 id1_0_,mediaanaly0_.med_id 作为 med_id3_0_,mediaanaly0_.success 作为success2_0_ from mea_media_analysis mediaanaly0_ left outer join med_media media1_ on mediaanaly0_.med_id=media1_.id where media1_.id=?

TRACE o.h.t.d.s.BasicBinder:65 - 绑定参数 [1] 为 [BIGINT] - [1]

调试 ohSQL:92 - 选择 media0_.id 作为 id2_1_0_,media0_.author 作为 author3_1_0_,media0_.link 作为 link4_1_0_,media0_.title 作为 title5_1_0_,media0_.body 作为 body6_1_0_,media0_.type 作为 type1_1_0_,mediaanaly1_.id 作为id1_0_1_, mediaanaly1_.med_id as med_id3_0_1_, mediaanaly1_.success as success2_0_1_ from med_media media0_ left outer join mea_media_analysis mediaanaly1_ on media0_.id=mediaanaly1_.med_id where media0_.id=?

TRACE o.h.t.d.s.BasicBinder:65 - 绑定参数 [1] 为 [BIGINT] - [1]

调试 ohSQL:92 - 选择 mediaanaly0_.id 作为 id1_0_1_,mediaanaly0_.med_id 作为 med_id3_0_1_,mediaanaly0_.success 作为 success2_0_1_,media1_.id 作为 id2_1_0_,media1_.author 作为 author3_1_0_,media1_.link 作为 link4_1_0_,media1_.title 作为title5_1_0_, media1_.body as body6_1_0_, media1_.type as type1_1_0_ from mea_media_analysis mediaanaly0_ inner join med_media media1_ on mediaanaly0_.med_id=media1_.id where mediaanaly0_.med_id=?

TRACE o.h.t.d.s.BasicBinder:65 - 绑定参数 [1] 为 [BIGINT] - [1]

【问题讨论】:

  • 删除@Query 时看到的查询是什么?
  • @Kirinya 添加了额外的查询:)
  • 你可以试试@OneToOne(optional = false)吗?我认为JoinColumnnullable 字段仅在您自动生成表的情况下更改DDL,而不是执行的SQL - @OneToOne(optional = false) 正在做什么
  • 您有两种方式绑定。你就不能aNewsReport.getAnalysis()吗?
  • 做这个查询的时候日志里还有其他的查询吗?当您拥有 EAGER(@OneToOne 默认为 EAGER)、FETCH、optional=false 和 Inheritance 组合时,Hibernate 可以触发许多并行查询。按照查询的顺序可以帮助您理解场景。

标签: spring hibernate spring-data-jpa jpql


【解决方案1】:

我尝试在dedicated branch in github 中重现您的问题。除了实体类中的 getter setter 之外,我对上面的代码示例没有任何更改,尤其是对于 Repository 类。从simple unit test code 向我展示的内容来看,您的查询没有问题,并且只生成了 1 个查询,如下所示:

---------- WATCH GENERATED QUERY HERE ----------
2018-01-19 09:20:26.880 DEBUG 22769 --- [main] org.hibernate.SQL : select mediaanaly0_.id as id1_1_0_, media1_.id as id2_0_1_, mediaanaly0_.content as content2_1_0_, mediaanaly0_.med_id as med_id4_1_0_, mediaanaly0_.success as success3_1_0_, media1_.author as author3_0_1_, media1_.link as link4_0_1_, media1_.title as title5_0_1_, media1_.body as body6_0_1_, media1_.type as type1_0_1_ from media_analysis mediaanaly0_ left outer join media media1_ on mediaanaly0_.med_id=media1_.id where mediaanaly0_.med_id=?
---------- END OF GENERATED QUERY HERE ----------

不过,您的问题出现的原因可能是:

  1. 您当前使用的 JPA/Hibernate/Spring Data 版本中存在错误(此处未提及具体版本)。
  2. 在另一层(服务、控制器层)的代码中进行了不必要的调用。

另一件可能有帮助的事情是,您可以在您的关系中添加 @Fetch(FetchMode.JOIN) 注释,例如:

@Fetch(FetchMode.JOIN)
@OneToOne
@JoinColumn(
    name = "MED_ID",
    nullable = false,
    foreignKey = @ForeignKey(name="MEA_MED_FK")
)
private Media media;

但我强烈建议您检查和/或升级您的库版本,和/或编写数据库测试以确保问题究竟出在哪里。

HTH。

【讨论】:

    【解决方案2】:

    我没有使用 JpaRepository 的经验,只是(很多)使用 Hibernate(主要是和 CriteriaBuilder),但有一些想法:

    • 尝试仅在两个实体之一中映射 @OneToOne(根据您的示例应该是 MediaAnalysis)
    • 尝试将MediaAnalysis 映射为1:n 关系(好像每个媒体可能有多个MediaAnalysis,这很可能在我的理解中,只是可能不在您的域中)。

    看看这是否有助于生成查询。

    【讨论】:

      【解决方案3】:

      您可以实现只执行一个数据库查询的一种方法是,如果您仅在所有者方指定@OneToOne 关系,据我了解是MediaAnalysis 实体。

      public class MediaAnalysis {
      
          // ...
          @OneToOne(cascade = CascadeType.ALL)
          @JoinColumn(name = "MED_ID")
          private Media media;
          // ...
      }
      

      并从Media 实体中完全删除analysis 字段。

      刚刚试了一下,它按预期工作,在测试中执行的唯一查询是:

      select mediaanaly0_.id as id1_3_0_, 
             media1_.id as id2_2_1_, 
             mediaanaly0_.content as content2_3_0_, 
             mediaanaly0_.med_id as med_id4_3_0_, 
             mediaanaly0_.success as success3_3_0_, 
             media1_.author as author3_2_1_, 
             media1_.link as link4_2_1_, 
             media1_.title as title5_2_1_, 
             media1_.body as body6_2_1_, 
             media1_.type as type1_2_1_ 
      from mediaanalysis mediaanaly0_ 
      left outer join media media1_ on mediaanaly0_.med_id=media1_.id 
      where mediaanaly0_.med_id=?
      

      您可以在github repository找到示例代码。

      【讨论】:

        最近更新 更多