Chętne pobieranie JPA nie dołącza

112

Co dokładnie kontroluje strategia pobierania JPA? Nie potrafię dostrzec różnicy między chętnym a leniwym. W obu przypadkach JPA / Hibernate nie łączy automatycznie relacji „wiele do jednego”.

Przykład: osoba ma jeden adres. Adres może należeć do wielu osób. Klasy jednostek z adnotacjami JPA wyglądają następująco:

@Entity
public class Person {
    @Id
    public Integer id;

    public String name;

    @ManyToOne(fetch=FetchType.LAZY or EAGER)
    public Address address;
}

@Entity
public class Address {
    @Id
    public Integer id;

    public String name;
}

Jeśli używam zapytania JPA:

select p from Person p where ...

JPA / Hibernate generuje jedno zapytanie SQL do wyboru z tabeli Person, a następnie odrębne zapytanie adresowe dla każdej osoby:

select ... from Person where ...
select ... from Address where id=1
select ... from Address where id=2
select ... from Address where id=3

Jest to bardzo złe w przypadku dużych zestawów wyników. Jeśli jest 1000 osób, generuje 1001 zapytań (1 od osoby i 1000 niezależnie od adresu). Wiem o tym, ponieważ patrzę na dziennik zapytań MySQL. Zrozumiałem, że ustawienie typu pobierania adresu na chętny spowoduje, że JPA / Hibernate automatycznie zapyta o sprzężenie. Jednak niezależnie od typu pobierania nadal generuje odrębne zapytania dotyczące relacji.

Dopiero gdy wyraźnie powiem mu, aby dołączył, faktycznie dołączy:

select p, a from Person p left join p.address a where ...

Czy coś mi umyka? Teraz muszę ręcznie zakodować każde zapytanie, aby zostało dołączone do relacji „wiele do jednego”. Używam implementacji JPA Hibernate z MySQL.

Edycja: Wygląda na to, że (patrz FAQ Hibernacji tutaj i tutaj ) FetchTypenie wpływa na zapytania JPA. Więc w moim przypadku wyraźnie nakazałem mu dołączyć.

Steve Kuo
źródło
5
Linki do wpisów FAQ są zepsute, tutaj działa jeden
n0weak

Odpowiedzi:

99

JPA nie dostarcza żadnych specyfikacji dotyczących mapowania adnotacji w celu wybrania strategii pobierania. Ogólnie rzecz biorąc, powiązane podmioty można pobrać na jeden z poniższych sposobów

  • SELECT => jedno zapytanie dla jednostek głównych + jedno zapytanie dla powiązanej zmapowanej jednostki / kolekcji każdej jednostki głównej = (n + 1) zapytań
  • SUBSELECT => jedno zapytanie dla jednostek głównych + drugie zapytanie dla pokrewnej zmapowanej jednostki / kolekcji wszystkich jednostek głównych pobranych w pierwszym zapytaniu = 2 zapytania
  • JOIN => jedno zapytanie w celu pobrania zarówno jednostek głównych, jak i wszystkich ich mapowanych jednostek / kolekcji = 1 zapytanie

A więc są dwie skrajności SELECTi JOINmieszczą SUBSELECTsię pomiędzy. Można wybrać odpowiednią strategię w oparciu o model swojej domeny.

Domyślnie SELECTjest używany przez JPA / EclipseLink i Hibernate. Można to zmienić, używając:

@Fetch(FetchMode.JOIN) 
@Fetch(FetchMode.SUBSELECT)

w Hibernate. Pozwala również na SELECTjawne ustawienie trybu za pomocą @Fetch(FetchMode.SELECT)którego można dostroić używając rozmiaru partii np @BatchSize(size=10).

Odpowiednie adnotacje w EclipseLink to:

@JoinFetch
@BatchFetch
Yadu
źródło
5
Dlaczego te ustawienia istnieją? Myślę, że JOIN trzeba używać prawie zawsze. Teraz muszę oznaczyć wszystkie mapowania adnotacjami dotyczącymi hibernacji.
vbezhenar
4
Ciekawe, ale niestety @Fetch (FetchMode.JOIN) w ogóle nie działa dla mnie (Hibernate 4.2.15), w JPQL, jak w Criteria.
Aphax
3
Adnotacja Hibernate też wydaje mi się nie działać, używając Spring JPA
TheBakker
2
@Aphax Może to być spowodowane tym, że Hibernate używa innych domyślnych strategii JPAQL / Criteria w porównaniu z em.find (). Zobacz vladmihalcea.com/2013/10/17/ ... i dokumentację referencyjną.
Joshua Davis
1
@vbezhenar (i jeszcze raz czytając jego komentarz jakiś czas później): zapytanie JOIN generuje produkt kartezjański w bazie danych, więc powinieneś być pewien, że chcesz, aby ten produkt kartezjański został obliczony. Zauważ, że jeśli użyjesz połączenia pobierania, nawet jeśli ustawisz LAZY, będzie on chętnie ładowany.
Walfrat
45

„mxc” ma rację. fetchTypepo prostu określa, kiedy relacja powinna zostać rozwiązana.

Aby zoptymalizować przyspieszone ładowanie za pomocą połączenia zewnętrznego, musisz dodać

@Fetch(FetchMode.JOIN)

na swoim polu. To jest adnotacja dotycząca hibernacji.

Rudolfson
źródło
6
To nie działa dla mnie z Hibernate 4.2.15, w JPQL lub Criteria.
Aphax
6
@Aphax Myślę, że to dlatego, że JPAQL i kryteria nie są zgodne ze specyfikacją pobierania. Adnotacja Fetch działa tylko dla em.find (), AFAIK. Zobacz vladmihalcea.com/2013/10/17/… Zobacz także dokumentację dotyczącą hibernacji. Jestem prawie pewien, że jest to gdzieś omówione.
Joshua Davis
@JoshuaDavis Chodzi mi o to, że adnotacja \ @Fetch nie stosuje żadnej optymalizacji JOIN w zapytaniach, czy JPQL lub em.find (), właśnie spróbowałem jeszcze raz Hibernate 5.2. + I nadal jest to to samo
Aphax
38

Atrybut fetchType określa, czy pole z adnotacjami jest pobierane natychmiast po pobraniu jednostki podstawowej. Niekoniecznie decyduje o tym, jak skonstruowana jest instrukcja pobierania, rzeczywista implementacja sql zależy od dostawcy, z którego korzystasz, linku górnego / hibernacji itp.

Jeśli ustawisz fetchType=EAGEROznacza to, że pole z adnotacjami jest wypełniane swoimi wartościami w tym samym czasie, co inne pola w encji. Więc jeśli otworzysz uprawnionego menedżera, pobierz obiekty swojej osoby, a następnie zamkniesz uprawnionego, następnie wykonanie polecenia person.address nie spowoduje wyrzucenia leniwego obciążenia.

Jeśli ustawisz, fetchType=LAZYpole jest wypełniane tylko wtedy, gdy jest dostępne. Jeśli wcześniej zamknąłeś uprawnionego menedżera, wyjątek leniwego ładowania zostanie wyrzucony, jeśli wykonasz polecenie person.address. Aby załadować pole, musisz umieścić jednostkę z powrotem w kontekście uprawniających za pomocą em.merge (), a następnie uzyskać dostęp do pola, a następnie zamknąć menadżera uprawnień.

Możesz chcieć leniwe ładowanie podczas konstruowania klasy klienta z kolekcją dla zamówień klientów. Jeśli pobierałeś każde zamówienie dla klienta, gdy chciałeś uzyskać listę klientów, może to być kosztowna operacja na bazie danych, gdy szukasz tylko nazwy klienta i danych kontaktowych. Najlepiej zostawić dostęp do bazy danych na później.

Druga część pytania - jak przejść w stan hibernacji, aby wygenerować zoptymalizowany SQL?

Hibernate powinno pozwolić ci na udzielenie wskazówek, jak skonstruować najbardziej wydajne zapytanie, ale podejrzewam, że coś jest nie tak z twoją tabelą. Czy związek jest ustalony w tabelach? Hibernate mógł zdecydować, że proste zapytanie będzie szybsze niż łączenie, zwłaszcza jeśli brakuje indeksów itp.

mxc
źródło
20

Spróbuj z:

select p from Person p left join FETCH p.address a where...

U mnie działa podobnie z JPA2 / EclipseLink, ale wygląda na to, że ta funkcja jest również obecna w JPA1 :

sinuhepop
źródło
7

Jeśli używasz EclipseLink zamiast Hibernacji, możesz zoptymalizować swoje zapytania za pomocą „wskazówek dotyczących zapytań”. Zobacz ten artykuł z Eclipse Wiki: EclipseLink / Examples / JPA / QueryOptimization .

Jest rozdział na temat „Połączonego czytania”.

uı6ʎɹnɯ ꞁəıuɐp
źródło
2

aby dołączyć, możesz zrobić wiele rzeczy (używając eclipselink)

  • w jpql możesz zrobić lewe dołączenie pobierania

  • w nazwanym zapytaniu można określić wskazówkę dotyczącą zapytania

  • w TypedQuery możesz powiedzieć coś w rodzaju

    query.setHint("eclipselink.join-fetch", "e.projects.milestones");

  • jest też wskazówka dotycząca pobierania zbiorczego

    query.setHint("eclipselink.batch", "e.address");

widzieć

http://java-persistence-performance.blogspot.com/2010/08/batch-fetching-optimizing-object-graph.html

Kalpesh Soni
źródło
1

Miałem dokładnie ten problem z wyjątkiem tego, że klasa Person miała osadzoną klasę klucza. Moim rozwiązaniem było dołączenie ich do zapytania ORAZ usunięcie

@Fetch(FetchMode.JOIN)

Moja osadzona klasa identyfikatora:

@Embeddable
public class MessageRecipientId implements Serializable {

    @ManyToOne(targetEntity = Message.class, fetch = FetchType.LAZY)
    @JoinColumn(name="messageId")
    private Message message;
    private String governmentId;

    public MessageRecipientId() {
    }

    public Message getMessage() {
        return message;
    }

    public void setMessage(Message message) {
        this.message = message;
    }

    public String getGovernmentId() {
        return governmentId;
    }

    public void setGovernmentId(String governmentId) {
        this.governmentId = governmentId;
    }

    public MessageRecipientId(Message message, GovernmentId governmentId) {
        this.message = message;
        this.governmentId = governmentId.getValue();
    }

}
Gunslinger
źródło
-1

Przyszły mi na myśl dwie rzeczy.

Po pierwsze, czy na pewno masz na myśli ManyToOne jako adres? Oznacza to, że wiele osób będzie miało ten sam adres. Jeśli jest edytowany dla jednego z nich, zostanie zmieniony dla wszystkich. Czy taki jest twój zamiar? 99% adresów jest „prywatnych” (w tym sensie, że należą tylko do jednej osoby).

Po drugie, czy masz inne chętne relacje z bytem Person? Jeśli dobrze pamiętam, Hibernate może obsłużyć tylko jedną gorliwą relację na jednostce, ale jest to prawdopodobnie nieaktualna informacja.

Mówię to, ponieważ twoje zrozumienie, jak to powinno działać, jest zasadniczo poprawne z miejsca, w którym siedzę.

cletus
źródło
To zmyślony przykład, w którym zastosowano wiele do jednego. Adres osoby mógł nie być najlepszym przykładem. W moim kodzie nie widzę żadnych innych żądnych typów pobierania.
Steve Kuo
Proponuję więc zredukować to do prostego przykładu, który działa i robi to, co widzisz, a następnie publikuje to. W modelu mogą występować inne komplikacje, które powodują nieoczekiwane zachowanie.
cletus
1
Uruchomiłem kod dokładnie tak, jak jest przedstawiony powyżej i wykazuje wspomniane zachowanie.
Steve Kuo