Eager fetch JPA tidak bergabung

112

Apa sebenarnya yang dikontrol strategi pengambilan JPA? Saya tidak dapat mendeteksi perbedaan apa pun antara bersemangat dan malas. Dalam kedua kasus JPA / Hibernate tidak secara otomatis menggabungkan hubungan banyak-ke-satu.

Contoh: Orang memiliki satu alamat. Sebuah alamat bisa menjadi milik banyak orang. Kelas entitas beranotasi JPA terlihat seperti:

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

Jika saya menggunakan kueri JPA:

select p from Person p where ...

JPA / Hibernate menghasilkan satu kueri SQL untuk dipilih dari tabel Orang, dan kemudian kueri alamat berbeda untuk setiap orang:

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

Ini sangat buruk untuk set hasil yang besar. Jika ada 1000 orang, itu menghasilkan 1001 kueri (1 dari Orang dan 1000 berbeda dari Alamat). Saya tahu ini karena saya melihat log kueri MySQL. Itu adalah pemahaman saya bahwa pengaturan tipe fetch alamat menjadi bersemangat akan menyebabkan JPA / Hibernate secara otomatis melakukan kueri dengan bergabung. Namun, apa pun jenis pengambilannya, ini tetap menghasilkan kueri yang berbeda untuk hubungan.

Hanya ketika saya secara eksplisit menyuruhnya untuk bergabung, apakah itu benar-benar bergabung:

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

Apakah saya melewatkan sesuatu di sini? Saya sekarang harus memberikan kode untuk setiap kueri sehingga meninggalkan hubungan banyak-ke-satu. Saya menggunakan implementasi JPA Hibernate dengan MySQL.

Sunting: Tampaknya (lihat FAQ Hibernasi di sini dan di sini ) yang FetchTypetidak memengaruhi kueri JPA. Jadi dalam kasus saya, saya secara eksplisit menyuruhnya bergabung.

Steve Kuo
sumber
5
Tautan ke entri FAQ rusak, di sini berfungsi satu
n0weak

Jawaban:

99

JPA tidak memberikan spesifikasi apa pun tentang anotasi pemetaan untuk memilih strategi pengambilan. Secara umum, entitas terkait dapat diambil dengan salah satu cara yang diberikan di bawah ini

  • SELECT => satu kueri untuk entitas akar + satu kueri untuk entitas yang dipetakan terkait / kumpulan dari setiap entitas akar = (n + 1) kueri
  • SUBSELECT => satu kueri untuk entitas akar + kueri kedua untuk entitas yang dipetakan terkait / kumpulan dari semua entitas akar yang diambil dalam kueri pertama = 2 kueri
  • JOIN => satu kueri untuk mengambil kedua entitas akar dan semua entitas yang dipetakan / kumpulan = 1 kueri

Jadi SELECTdan JOINadalah dua ekstrem dan SUBSELECTberada di antara keduanya. Seseorang dapat memilih strategi yang sesuai berdasarkan model domainnya.

Secara default SELECTdigunakan oleh JPA / EclipseLink dan Hibernate. Ini dapat diganti dengan menggunakan:

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

dalam mode Hibernate. Ini juga memungkinkan untuk mengatur SELECTmode secara eksplisit @Fetch(FetchMode.SELECT)yang dapat disetel dengan menggunakan ukuran batch misalnya @BatchSize(size=10).

Anotasi terkait di EclipseLink adalah:

@JoinFetch
@BatchFetch
Yadu
sumber
5
Mengapa pengaturan tersebut ada? Saya pikir JOIN harus digunakan hampir selalu. Sekarang saya harus menandai semua pemetaan dengan anotasi khusus hibernasi.
vbezhenar
4
Menarik tapi sayangnya @Fetch (FetchMode.JOIN) tidak berfungsi sama sekali untuk saya (Hibernate 4.2.15), dalam JPQL seperti pada Kriteria.
Aphax
3
Anotasi Hibernate tampaknya juga tidak berfungsi sama sekali untuk saya, menggunakan Spring JPA
TheBakker
2
@Aphax Ini mungkin karena Hibernate menggunakan strategi default yang berbeda untuk JPAQL / Criteria vs em.find (). Lihat vladmihalcea.com/2013/10/17/… dan dokumentasi referensi.
Joshua Davis
1
@vbezhenar (dan lainnya membaca komentarnya beberapa waktu kemudian): Query GABUNG menghasilkan produk cartesian di database sehingga Anda harus yakin bahwa Anda ingin produk cartesian tersebut dihitung. Perhatikan bahwa jika Anda menggunakan fetch join, bahkan jika Anda meletakkan LAZY, itu akan dimuat dengan bersemangat.
Walfrat
45

"mxc" benar. fetchTypehanya menentukan kapan relasi harus diselesaikan.

Untuk mengoptimalkan eager loading dengan menggunakan gabungan luar, Anda harus menambahkan

@Fetch(FetchMode.JOIN)

ke bidang Anda. Ini adalah anotasi khusus hibernasi.

rudolfson
sumber
6
Ini tidak berfungsi untuk saya dengan Hibernate 4.2.15, dalam JPQL atau Kriteria.
Aphax
6
@Aphax Saya pikir itu karena JPAQL dan Kriteria tidak mematuhi spesifikasi Ambil. Anotasi Ambil hanya bekerja untuk em.find (), AFAIK. Lihat vladmihalcea.com/2013/10/17/… Juga, lihat dokumen hibernate. Saya cukup yakin ini tercakup di suatu tempat.
Joshua Davis
@JoshuaDavis Yang saya maksud adalah bahwa penjelasan \ @Fetch tidak menerapkan jenis pengoptimalan JOIN apa pun dalam kueri, baik JPQL atau em.find (), saya baru saja mencoba Hibernate 5.2. + Dan itu masih sama
Aphax
38

Atribut fetchType mengontrol apakah kolom beranotasi diambil segera saat entitas utama diambil. Itu tidak selalu menentukan bagaimana pernyataan fetch dibangun, implementasi sql sebenarnya tergantung pada penyedia yang Anda gunakan toplink / hibernate dll.

Jika Anda mengatur fetchType=EAGERIni berarti bahwa bidang beranotasi diisi dengan nilainya pada saat yang sama dengan bidang lain di entitas. Jadi, jika Anda membuka pengelola entitas, ambil objek person Anda dan kemudian tutup pengelola entitas, selanjutnya melakukan person.address tidak akan mengakibatkan pengecualian pemuatan lambat dilemparkan.

Jika Anda mengatur fetchType=LAZYbidang hanya diisi saat diakses. Jika Anda telah menutup entitymanager saat itu, pengecualian lazy load akan muncul jika Anda melakukan person.address. Untuk memuat bidang, Anda perlu meletakkan entitas kembali ke konteks entitasmangers dengan em.merge (), lalu lakukan akses bidang dan kemudian tutup manajer entitas.

Anda mungkin ingin pemuatan lambat saat membuat kelas pelanggan dengan koleksi untuk pesanan pelanggan. Jika Anda mengambil setiap pesanan untuk pelanggan saat Anda ingin mendapatkan daftar pelanggan, ini mungkin operasi database yang mahal saat Anda hanya mencari nama pelanggan dan detail kontak. Sebaiknya tinggalkan akses db sampai nanti.

Untuk bagian kedua dari pertanyaan - bagaimana mendapatkan hibernate untuk menghasilkan SQL yang dioptimalkan?

Hibernate memungkinkan Anda memberikan petunjuk tentang cara membuat kueri yang paling efisien, tetapi saya curiga ada yang salah dengan konstruksi tabel Anda. Apakah hubungan ditetapkan dalam tabel? Hibernate mungkin telah memutuskan bahwa kueri sederhana akan lebih cepat daripada bergabung terutama jika indeks dll hilang.

mxc
sumber
20

Coba dengan:

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

Ini berfungsi untuk saya serupa dengan JPA2 / EclipseLink, tetapi tampaknya fitur ini juga ada di JPA1 :

sinuhepop
sumber
7

Jika Anda menggunakan EclipseLink dan bukan Hibernate, Anda dapat mengoptimalkan kueri Anda dengan "petunjuk permintaan". Lihat artikel ini dari Eclipse Wiki: EclipseLink / Example / JPA / QueryOptimization .

Ada bab tentang "Membaca Bersama".

uı6ʎɹnɯ ꞁəıuɐp
sumber
2

untuk bergabung kamu bisa melakukan banyak hal (menggunakan eclipselink)

  • di jpql Anda bisa melakukan left join fetch

  • dalam kueri bernama Anda dapat menentukan petunjuk kueri

  • di TypedQuery, Anda bisa mengatakan sesuatu seperti

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

  • ada juga petunjuk pengambilan batch

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

Lihat

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

Kalpesh Soni
sumber
1

Saya memiliki masalah ini dengan pengecualian bahwa kelas Person memiliki kelas kunci yang disematkan. Solusi saya sendiri adalah bergabung dengan mereka dalam kueri DAN hapus

@Fetch(FetchMode.JOIN)

Kelas id saya yang disematkan:

@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
sumber
-1

Dua hal terjadi pada saya.

Pertama, apakah Anda yakin yang Anda maksud ManyToOne untuk alamat? Artinya, beberapa orang akan memiliki alamat yang sama. Jika diedit untuk salah satu dari mereka, itu akan diedit untuk semuanya. Apa itu maksudmu? 99% alamat waktu bersifat "pribadi" (dalam arti bahwa alamat tersebut hanya dimiliki oleh satu orang).

Kedua, apakah Anda memiliki hubungan bersemangat lainnya di entitas Person? Jika saya ingat dengan benar, Hibernate hanya dapat menangani satu hubungan yang bersemangat pada suatu entitas tetapi itu mungkin informasi yang sudah ketinggalan zaman.

Saya mengatakan itu karena pemahaman Anda tentang bagaimana ini seharusnya bekerja pada dasarnya benar dari tempat saya duduk.

cletus
sumber
Ini adalah contoh buatan yang menggunakan banyak-ke-satu. Alamat orang mungkin bukan contoh terbaik. Saya tidak melihat jenis eager fetch lainnya di kode saya.
Steve Kuo
Saran saya kemudian adalah untuk mengurangi ini menjadi contoh sederhana yang berjalan dan melakukan apa yang Anda lihat dan kemudian mempostingnya. Mungkin ada komplikasi lain dalam model Anda yang menyebabkan perilaku yang tidak terduga.
cletus
1
Saya menjalankan kode persis seperti yang muncul di atas dan kode tersebut menunjukkan perilaku tersebut.
Steve Kuo