2010-08-01 13 views
8

Mam encję A, która ma - jednostkę B, a B ma - A z @OneToOne dwukierunkową.Dlaczego hibernacja wykonuje dwa zapytania w celu szybkiego załadowania asocjacji dwukierunkowej @OneToOne?

Teraz, kiedy FindAll rekordy, hibernacja wykonać dwie kwerendy z lewego sprzężenia zewnętrznego na B, coś takiego:

select a.id, a.id_b, a.field1, b.id, b.field1 from A as a, B as b left outer join b ON b.id=a.id_b; 
select a.id, a.id_b, a.field1, b.id, b.field1 from A as a, B as b left outer join b ON b.id=a.id_b WHERE b.id=? 

pól pierwszy ładunek zapytanie A i B i to jest ok, ale dlaczego wykonać drugie zapytanie, aby przeładować A? Myślę, że to zapytanie ładuje zawartość A w B, ale to A jest nieuchronnie A, które zawiera B ... więc jego już załadowane pierwszym zapytaniem, nie jest prawdziwe?

- EDIT -

Podmiot A:

@Entity 
public class A implements Serializable{ 
    // id and other ecc ecc 
    @OneToOne 
    @JoinColumn(name="id_b") 
    B b; 
} 

Podmiot B:

@Entity 
public class B implements Serializable{ 
    // id and other ecc ecc 
    @OneToOne(mappedBy="b") 
    A a; 
} 

Jest to sytuacja, a findAll na potrzebę dwa zapytania .. . czemu?

Odpowiedz

6

Blow, jeśli A i B akcji tej samej kolumnie klucz podstawowy gdzie oba podmioty są połączone za pomocą ich podstawowym kluczem, należy użyć @PrimaryKeyJoinColumn zamiast

@Entity 
public class A implements Serializable { 

    private MutableInt id = new MutableInt(); 

    private B b; 

    public void setIdAsMutableInt(MutableInt id) { 
     this.id = id; 
    } 

    @Id 
    @GeneratedValue 
    public Integer getId() { 
     return id.intValue(); 
    } 

    public void setId(Integer id) { 
     this.id.setValue(id); 
    } 

    /** 
     * Any ToOne annotation, such as @OneToOne and @ManyToOne, is EARGELY loaded, by default 
     */ 
    @OneToOne(fetch=FetchType.LAZY) 
    @PrimaryKeyJoinColumn 
    @Cascade(CascadeType.SAVE_UPDATE) 
    public B getB() { 
     return b; 
    } 

    public void setB(B b) { 
     b.setIdAsMutableInt(id); 

     this.b = b; 
    } 

} 

A B Wskazówka robisz nie trzeba mappedBy atrybut powodu @PrimaryKeyJoinColumn

@Entity 
public class B implements Serializable { 

    private MutableInt id = new MutableInt(); 

    private A a; 

    public void setIdAsMutableInt(MutableInt id) { 
     this.id = id; 
    } 

    @Id 
    public Integer getId() { 
     return id.intValue(); 
    } 

    public void setId(Integer id) { 
     this.id.setValue(id); 
    } 

    @OneToOne(fetch=FetchType.LAZY) 
    @PrimaryKeyJoinColumn 
    public A getA() { 
     return a; 
    } 

    public void setA(A a) { 
     this.a = a; 
    } 

} 
Badanie

Miejmy (można przetestować jeśli chcesz)

A a = new A(); 
B b = new B(); 

a.setB(b); 

/** 
    * b property will be saved because Cascade.SAVE_UPDATE 
    */ 
Serializable id = session.save(a); 

b = (B) session 
     .createQuery("from B b left join fetch b.a where b.id = :id") 
     .setParameter("id", id) 
     .list() 
     .get(0); 

Assert.assertEquals(b.getId(), b.getA().getId()); 

Wskazówka Używam pole MutableInt (zamknięty przez właściwości Integer) zamiast Integer ponieważ Integer jest niezmienna Rodzaj jako sposób A i akcji B SAME przypisany identyfikator

Ale jeśli a i B są połączone za pomocą innych niż ich pierwotnej klucza, należy użyć @JoinColumn i mappedBy (związek dwukierunkowy, z prawej) w następujący sposób

@Entity 
public class A implements Serializable { 

    private Integer id; 

    private B b; 

    @Id 
    @GeneratedValue 
    public Integer getId() { 
     return id; 
    } 

    public void setId(Integer id) { 
     this.id = id; 
    } 

    /** 
     * mappedBy="a" means: Look at "a" field/property at B Entity. If it has any assigned value, join us Through B_ID foreign key column 
     */ 
    @OneToOne(fetch=FetchType.LAZY, mappedBy="a") 
    /** 
     * Table A has a foreign key column called "B_ID" 
     */ 
    @JoinColumn(name="B_ID") 
    @Cascade(CascadeType.SAVE_UPDATE) 
    public B getB() { 
     return b; 
    } 

    public void setB(B b) { 
     this.b = b; 
    } 

} 

A B

@Entity 
public class B implements Serializable { 

    private Integer id; 

    private A a; 

    public void setIdAsMutableInt(MutableInt id) { 
     this.id = id; 
    } 

    @Id 
    @GeneratedValue 
    public Integer getId() { 
     return id; 
    } 

    public void setId(Integer id) { 
     this.id = id; 
    } 

    @OneToOne(fetch=FetchType.LAZY) 
    public A getA() { 
     return a; 
    } 

    public void setA(A a) { 
     this.a = a; 
    } 

} 

Aby przetestować

A a = new A(); 
B b = new B(); 

/** 
    * Set up both sides 
    * Or use some kind of add convenience method 
    */ 
a.setB(b); 
b.setA(a); 

/** 
    * b property will be saved because Cascade.SAVE_UPDATE 
    */ 
Serializable id = session.save(a); 

b = (B) session 
     .createQuery("from B b left join fetch b.a where b.id = :id") 
     .setParameter("id", id) 
     .list() 
     .get(0); 

Korzystając z boku właściciel B, dostaniesz dwa select Występuje ponieważ B Tabela nie zawiera klucz obcy kolumna, która wskazuje na tabelę A Ale za pomocą

„od A lewy dołączyć sprowadzić ab gdzie a.id =: id”

Dostaniesz tylko jeden SELECT, ponieważ wie, jak odzyskać swoje dołączył B przy użyciu jego B_ID kolumny klucza obcego

+0

Dziękuję! Bardzo wyczerpujący! – blow

0

Co dokładnie wygląda Twoje mapowanie?

Czy twoje zajęcia A i B poprawnie wdrożyć hashCode() i equals() tak że można powiedzieć, że Hibernate instancja A wskazywanego przez B jest taka sama instancja pierwszej A?

Wygląda na to, że próbujesz utworzyć model odwzorowania dwukierunkowego jeden-do-jednego - zapoznaj się z informacjami na temat zalecanych metod jego wykonania.

+0

Edytowałem mój wpis. Tak, mam bardzo prostą implementację equals i hashCode na podstawie identyfikatora. Wszystkie moje przedsięwzięcia rozszerzają AbstractEntity o tę implementację. – blow