在一个 Table 上休眠两个 ManyToOne 关系,第一个获取 Eager,第二个加载 LAZY

Hibernate two ManyToOne relations on one Table, the first gets Eager and the second LAZY loaded

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

这是我的项目实体,其中包含应该加入的两个类别实体:

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

    @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 = "Whosebug_question")
public class Category extends WhosebugQuestionEntity {

    @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加入secondary category lazy,注解好像是一样的

我使用的hibernate版本是5.0.10.Final.

这是基础实体的样子:

@MappedSuperclass
abstract public class WhosebugQuestionEntity 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();
    }
}

这里有一个例子"query",正如我所说的,我正在使用hibernate criteria和HQL,这两种方法都会出现问题。

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

使用标准 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 尝试解析关联{site=site, id=null} 使用 TwoPhaseLoad。但是如果你写

@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();

尝试以下解决方案:

@Entity
@Table(name = "item", schema = "public", catalog = "Whosebug_question")
  @DynamicUpdate
  public class Item {
    
    @ManyToOne
    @JoinColumn(name="site")
    private Category primaryCategory;
    
    @ManyToOne
    @JoinColumn(name="site")
    private Category secondaryCategory;
  }

  @Entity
  @Table(name = "category", schema = "public", catalog = "Whosebug_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;
    
  }

我使用您的 类 和以下查询代码(使用 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