2017-12-05 157 views
9

Mam następujące jednostki, item, które mogą mieć maksymalnie dwie kategorie: podstawową i dodatkową. Obie kategorie są mapowane ManyToOne na tabelę category przy użyciu JoinColumnsOrFormulas. Pierwszy pobiera się zgodnie z oczekiwaniami, ale drugi nie występuje w instrukcji SQL i jest leniwy. To leniwy ładunek skutkuje klasycznym problemem n + 1.Hibernuj dwie relacje ManyToOne na jednej tabeli, pierwsza dostaje ochotę, a druga LAZY jest załadowana

To moja jednostka element z obu podmiotów kategorii, które powinny dostaje Rejestracja:

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

Jest to jednostka kategoria:

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

Powstały zapytanie zawiera jedynie kategoria podstawowy:

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; 

W związku z tym do drugiej kategorii dołącza leniwy:

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=?; 

Dlaczego Hibernate dołącza do leniwej kategorii drugorzędnej, adnotacje wydają się być takie same.

Wersja hibernacji, której używam, to 5.0.10.Final.

ten sposób jednostka bazowa wygląda następująco:

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

Oto przykład „zapytanie”, jak powiedział używam hibernacji kryteria, jak HQL, problem pojawia się z obu metod.

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

spróbować użyć standardowych @JoinColumns JPA .. zamiast hibernacji jeden –

+0

Wszelkich szczegółów w modelu bazy danych? I na przykład druga kategoria nullable? – Gimby

+0

Obie identyfikatory kategorii są zerowalne w modelu bazy danych: \ d pozycja primary_category_id | bigint secondary_category_id | bigint –

Odpowiedz

4

Przy standardowych WZP adnotacje to wyglądać następująco (zaktualizowanych):

@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: Uważam, że drugi select oświadczenie jest generowany tylko wtedy, gdy używasz join przez kompozyt key: Hibernate próbuje rozwiązać skojarzenia dla {site=site, id=null} przy użyciu TwoPhaseLoad. Ale jeśli piszesz

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

i secondary_category_id jest null następnie zostaną wygenerowane jedynym select oświadczenie, a wartość secondaryCategory będzie null. Może to jakoś ci pomoże.Na przykład, można dodać ograniczenia na site dziedzinie budując swoje kryteria:

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

Łączy się przy użyciu skompilowanych kluczy podstawowych: 'site' i' category_id'. –

+0

@ Hugo Dzięki za zauważenie! Rzeczywiście byłem Nieuważny. Zaktualizowałem moją odpowiedź –

+0

Używanie adnotacji JPA powoduje tylko problem, zapytanie zostaje zbudowane poprawnie, a obie kategorie są połączone.Stąd kategoria dodatkowa jest opcjonalna, wydaje się, że hibernacja próbuje leniwie załadować ją, jeśli "secondary_category_id" ma wartość "null". –

1

wypróbuj następujące rozwiązanie:

@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

Zrobiłem szybki test przy użyciu klas, a następujący kod zapytania (stosując kryteria WZP zapytania zamiast natywnej hibernacji)

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

wyniki w następujący SQL generowane (sformatowany dla czytelności):

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 

Jak widać, obie tabele kategorii są połączone w taki sam SELECT