【问题标题】:Hibernate two ManyToOne relations on one Table, the first gets Eager and the second LAZY loaded在一个表上休眠两个 ManyToOne 关系,第一个获取 Eager,第二个 LAZY 加载
【发布时间】:2017-12-05 15:07:20
【问题描述】:

我有以下实体,item 最多可以有两个类别,主要和次要。 这两个类别都使用JoinColumnsOrFormulas 映射到ManyToOnecategory 表。 第一个按预期获取EAGER,但第二个不会出现在 SQL 语句中并被延迟加载。 这种延迟加载会导致经典的 n+1 问题。

这是我的项目实体,两个类别实体都应该加入:

@Entity
@Table(name = "item", schema = "public", catalog = "stackoverflow_question")
@DynamicUpdate
public class Item extends StackOverflowQuestionEntity {

    @Id
    @Column(name = "id")
    protected Long id;

    @Column(name = "site")
    private String site;

    @ManyToOne
    @JoinColumnsOrFormulas({
            @JoinColumnOrFormula(formula = @JoinFormula(value = "site", referencedColumnName = "site")),
            @JoinColumnOrFormula(formula = @JoinFormula(value = "primary_category_id", referencedColumnName = "category_id"))
    })
    private Category primaryCategory;

    @Column(name = "primary_category_id")
    private Long primaryCategoryId;

    @ManyToOne
    @JoinColumnsOrFormulas({
            @JoinColumnOrFormula(formula = @JoinFormula(value = "site", referencedColumnName = "site")),
            @JoinColumnOrFormula(formula = @JoinFormula(value = "secondary_category_id", referencedColumnName = "category_id"))
    })
    private Category secondaryCategory;

    @Column(name = "secondary_category_id")
    private Long secondaryCategoryId;
}

这是类别实体:

@Entity
@Table(name = "category", schema = "public", catalog = "stackoverflow_question")
public class Category extends StackOverflowQuestionEntity {

    @Column(name = "category_id")
    private Long categoryId;

    @Column(name = "name")
    private String name;

    @Column(name = "site")
    private String site;
}

生成的查询仅包含主要类别:

SELECT this_.id AS id1_9_9_,
       this_.inserted AS inserted2_9_9_,
       this_.updated AS updated3_9_9_,
       this_.primary_category_id AS formula174_9_,
       this_.secondary_category_id AS formula176_9_,
       category2_.id AS id1_0_0_,
       category2_.inserted AS inserted2_0_0_,
       category2_.updated AS updated3_0_0_,
       category2_.name AS name7_0_0_
FROM public.item this_
LEFT OUTER JOIN public.category category2_ ON this_.site=category2_.site
AND this_.primary_category_id=category2_.category_id
WHERE True;

因此次要类别变得懒惰:

SELECT category0_.id AS id1_0_0_,
       category0_.inserted AS inserted2_0_0_,
       category0_.updated AS updated3_0_0_,
       category0_.name AS name4_0_0_,
       category0_.site AS site5_0_0_
FROM public.category category0_
WHERE category0_.site=?
  AND category0_.category_id=?;

为什么Hibernate加入二级分类懒惰,注解好像都一样。

我使用的休眠版本是5.0.10.Final。

这是基本实体的样子:

@MappedSuperclass
abstract public class StackOverflowQuestionEntity implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", unique = true, insertable = true, updatable = false, nullable = false)
    protected Long id;

    @Type(type="LocalDateTime")
    @Column(name = "created", nullable = false, insertable = true, updatable = false)
    protected LocalDateTime created;

    @Type(type="LocalDateTime")
    @Column(name = "refreshed", nullable = false, insertable = true, updatable = true)
    protected LocalDateTime refreshed;

    @PreUpdate
    protected void onUpdate() {
        refreshed = now();
    }

    @PrePersist
    protected void onCreate() { 
        created = refreshed = now();
    }
}

这是一个“查询”示例,正如我所说我使用的是休眠条件以及 HQL,这两种方法都会出现问题。

session
    .createCriteria(Item.class)
    .add(eq("id", id))
    .uniqueResult();

【问题讨论】:

  • 尝试使用标准 jpa @JoinColumns.. 而不是休眠的
  • 数据库模型中的任何细节?例如,我第二类可以为空?
  • 如何加载Items?
  • 如何查询您的Items?只是"from Item",还是涉及更多内容?另外,请提供您的StackOverflowQuestionEntity 的来源,如果您有任何确定的内容,可以添加您对使用 JPA 注释的研究结果
  • @GuidoKrömer 你用来获取的代码是什么,你能把它包括进来吗?我认为这一切都不同

标签: java postgresql hibernate many-to-one eager


【解决方案1】:

使用标准 JPA 注释,它看起来像这样(更新):

@ManyToOne
@JoinColumns({
    @JoinColumn(name="site", referencedColumnName="site", insertable = false, updatable = false),
    @JoinColumn(name="primary_category_id", referencedColumnName="category_id", insertable = false, updatable = false)
})
private Category primaryCategory;

@ManyToOne
@JoinColumns({
    @JoinColumn(name="site", referencedColumnName="site", insertable = false, updatable = false),
    @JoinColumn(name="secondary_category_id", referencedColumnName="category_id", insertable = false, updatable = false)
})
private Category secondaryCategory;

更新:我发现第二个select 语句仅在您通过复合键使用join 时生成:Hibernate 尝试使用TwoPhaseLoad 解析{site=site, id=null} 的关联。但是如果你写

@ManyToOne
@JoinColumn(name="secondary_category_id")
private Category secondaryCategory;

secondary_category_idnull,则将生成唯一一个select 语句,secondaryCategory 的值将是null。也许它会以某种方式帮助你。例如,您可以在构建条件时对 site 字段添加约束:

Category c = (Category) session.createCriteria(Category.class)
    .add(Restrictions.eq("id", 1L)) // for example
    // here you define additional restriction on site field
    .createAlias("secondaryCategory", "sc", JoinType.LEFT_OUTER_JOIN, Restrictions.sqlRestriction("this_.site = {alias}.site"))
    .uniqueResult();

【讨论】:

  • 他正在使用组合主键加入:sitecategory_id
  • @Hugo 感谢您的关注!确实,我是不专心的。更新了我的答案
  • 使用 JPA 注释只会移动问题,查询得到正确构建,两个类别都加入了。因此,次要类别是可选的,如果secondary_category_idnull,似乎休眠会尝试延迟加载。
  • 它不会延迟加载,而是急切地加载,而是在单独的 select 语句中加载。只有当您使用复合键进行连接时,它才会这样做
  • @Guido Krömer 尝试仅通过secondary_category_id 加入,您将获得一个选择。然后您可以使用条件按站点添加约束。它有点hack,但我真的不知道是否存在其他方式,如果只是为了修改架构。
【解决方案2】:

尝试以下解决方案:

@Entity
@Table(name = "item", schema = "public", catalog = "stackoverflow_question")
  @DynamicUpdate
  public class Item {

    @ManyToOne
    @JoinColumn(name="site")
    private Category primaryCategory;

    @ManyToOne
    @JoinColumn(name="site")
    private Category primaryCategory;
  }

  @Entity
  @Table(name = "category", schema = "public", catalog = "stackoverflow_question")
  public class Category {

     @OneToMany(targetEntity=Item.class, mappedBy="primaryCategory", cascade=CascadeType.ALL)
      private List<Item> primaryCategoryList;

    @OneToMany(targetEntity=Item.class, mappedBy="secondaryCategory", cascade=CascadeType.ALL)
       private List<Item> secondaryCategoryList;

  }

【讨论】:

    【解决方案3】:

    我使用您的类和以下查询代码进行了快速测试(使用 JPA 条件查询而不是本机 Hibernate)

    CriteriaQuery<Item> cq = em.getCriteriaBuilder().createQuery(Item.class);
    EntityGraph<Item> entityGraph = em.createEntityGraph(Item.class);
    entityGraph.addSubgraph("primaryCategory", Category.class);
    entityGraph.addSubgraph("secondaryCategory", Category.class);
    List<Item> items = em.createQuery(cq.select(cq.from(Item.class)))
        .setHint("javax.persistence.loadgraph", entityGraph)
        .getResultList();
    

    导致生成以下 SQL(为便于阅读而格式化):

    select item0_.id as id1_1_0_, 
        category1_.id as id1_0_1_, 
        category2_.id as id1_0_2_, 
        item0_.site as site4_1_0_, 
        item0_.primary_category_id as primary_2_1_0_,
        item0_.secondary_category_id as secondar3_1_0_, 
        category1_.category_id as category2_0_1_, 
        category1_.name as name3_0_1_, 
        category1_.site as site4_0_1_, 
        category2_.category_id as category2_0_2_, 
        category2_.name as name3_0_2_, 
        category2_.site as site4_0_2_ 
    from item item0_ 
    left outer join category category1_
        on item0_.site=category1_.site
        and item0_.secondary_category_id=category1_.category_id 
    left outer join category category2_
        on item0_.site=category2_.site
        and item0_.primary_category_id=category2_.category_id
    

    如您所见,两个类别表都在同一个 SELECT 中连接

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-10-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-01-18
      • 1970-01-01
      相关资源
      最近更新 更多