2017-12-05 8 views
9

次のエンティティを持っています。itemには、プライマリとセカンダリの2つのカテゴリがあります。 両方のカテゴリはJoinColumnsOrFormulasを使用してをcategoryテーブルにマッピングします。 最初のものは期待どおりにEAGERをフェッチしますが、2番目のものはSQL文では発生せず、遅延ロードされます。 この遅延読み込みは、古典的なn + 1問題を引き起こします。1つのテーブルで2つのManyToOneリレーションをHibernateし、最初にEagerを取得し、2番目のLAZYをロードします

これは、参加しますする必要があり、両方のカテゴリのエンティティと私のアイテムの実体である:

@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.最終です。

これは基本エンティティがどのように見えるかです:私はHibernateの基準だけでなく、HQLを使用しています、問題は両方の方法で発生言ったようにここで

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

は、「クエリ」の例です。 (を更新)それは次のようになり、標準のJPAの注釈付き

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

同じSELECTで接合されている代わりに、データベースモデルに1つの –

+0

どれ詳細を休止状態の...標準のJPA @JoinColumnsを使用しようか?私は2番目のカテゴリはnullable例えば? – Gimby

+0

両方のカテゴリIDは、データベースモデルでNULL可能です。 \ dアイテム primary_category_id | bigint secondary_category_id | bigint –

答えて

4

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

UPDATE:は、私はあなたがコンポジットでjoinを使用する場合にのみ、第二select文が生成されることがわかりましたキー:Hibernateは、を使用して{site=site, id=null}の関連付けを解決しようとします。あなたは

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

secondary_category_idを書く場合でもnull 1つだけselect文が生成され、secondaryCategory値はnullになりますです。多分それはあなたに何とか役立つでしょう。

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(); 
+0

彼は組み立てられた主キー 'site'と' category_id'を使って結合しています。 –

+0

@Hugo気づいてくれてありがとう!確かに、私は怠惰でした。私の答えが –

+0

に更新されました。JPA注釈を使用すると問題が移動するだけで、両方のカテゴリが結合された状態でクエリが正しく構築されます。したがって、セカンダリカテゴリはオプションです。 'secondary_category_id'が' null'の場合、ハイバネーションは遅延ロードしようとします。 –

1

は、以下のソリューション試してみてください:

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

    } 
1

私は、あなたのクラスを使用して簡単なテストを行なったし、次のクエリのコードを(自分の基準を構築しながらたとえば、siteフィールド上の制約を追加することができます以下のSQLで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(); 

結果を使用して生成されている()読みやすくするためにフォーマット:

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 

あなたが見ることができるように、両方のカテゴリテーブルは

関連する問題