Panduan Pantas untuk Hibernate Property_lazy_load_no_trans Property

1. Gambaran keseluruhan

Semasa menggunakan pemuatan yang malas di Hibernate, kami mungkin menghadapi pengecualian, dengan mengatakan bahawa tidak ada sesi.

Dalam tutorial ini, kita akan membincangkan bagaimana menyelesaikan masalah pemuatan yang malas ini. Untuk melakukan ini, kami akan menggunakan Spring Boot untuk meneroka contoh.

2. Isu Pemuatan Malas

Tujuan pemuatan malas adalah untuk menjimatkan sumber dengan tidak memuat objek yang berkaitan ke dalam memori semasa kita memuat objek utama. Sebaliknya, kami menangguhkan inisialisasi entiti malas sehingga ia diperlukan. Hibernate menggunakan proksi dan pembungkus koleksi untuk melaksanakan pemuatan malas.

Semasa mengambil data yang dimuatkan dengan malas, terdapat dua langkah dalam prosesnya. Pertama, ada mengisi objek utama, dan kedua, mengambil data dalam proksi. Memuatkan data selalu memerlukan Sesi terbuka di Hibernate.

Masalahnya timbul apabila langkah kedua berlaku setelah transaksi ditutup , yang membawa kepada LazyInitializationException .

Pendekatan yang disarankan adalah merancang aplikasi kami untuk memastikan pengambilan data berlaku dalam satu transaksi. Tetapi, ini kadang-kadang sukar apabila menggunakan entiti malas di bahagian lain kod yang tidak dapat menentukan apa yang telah atau belum dimuat.

Hibernate mempunyai jalan penyelesaian, harta allow_lazy_load_no_trans . Menghidupkan ini bermaksud bahawa setiap pengambilan entiti malas akan membuka sesi sementara dan menjalankan transaksi yang berasingan.

3. Contoh Pemuatan Malas

Mari lihat tingkah laku pemalas malas di bawah beberapa senario.

3.1 Menubuhkan Entiti dan Perkhidmatan

Katakan kita mempunyai dua entiti, Pengguna dan Dokumen . Satu Pengguna mungkin mempunyai banyak Dokumen , dan kami akan menggunakan @OneToMany untuk menerangkan hubungan itu. Juga, kami akan menggunakan @Fetch (FetchMode.SUBSELECT) untuk kecekapan.

Kita harus perhatikan bahawa, secara lalai, @OneToMany mempunyai jenis pengambilan yang malas.

Sekarang mari kita tentukan entiti Pengguna kami :

@Entity public class User { // other fields are omitted for brevity @OneToMany(mappedBy = "userId") @Fetch(FetchMode.SUBSELECT) private List docs = new ArrayList(); }

Seterusnya, kita memerlukan lapisan perkhidmatan dengan dua kaedah untuk menggambarkan pilihan yang berbeza. Salah satunya diberi komen sebagai @Transactional . Di sini, kedua kaedah tersebut mempunyai logik yang sama dengan mengira semua dokumen dari semua pengguna:

@Service public class ServiceLayer { @Autowired private UserRepository userRepository; @Transactional(readOnly = true) public long countAllDocsTransactional() { return countAllDocs(); } public long countAllDocsNonTransactional() { return countAllDocs(); } private long countAllDocs() { return userRepository.findAll() .stream() .map(User::getDocs) .mapToLong(Collection::size) .sum(); } }

Sekarang, mari kita lihat tiga contoh berikut. Kami juga akan menggunakan SQLStatementCountValidator untuk memahami kecekapan penyelesaiannya, dengan menghitung jumlah pertanyaan yang dilaksanakan.

3.2. Pemuatan Malas Dengan Transaksi Sekeliling

Pertama sekali, mari kita gunakan pemuatan malas dengan cara yang disyorkan. Oleh itu, kami akan memanggil kaedah @Transactional kami di lapisan perkhidmatan:

@Test public void whenCallTransactionalMethodWithPropertyOff_thenTestPass() { SQLStatementCountValidator.reset(); long docsCount = serviceLayer.countAllDocsTransactional(); assertEquals(EXPECTED_DOCS_COLLECTION_SIZE, docsCount); SQLStatementCountValidator.assertSelectCount(2); }

Seperti yang kita lihat, ini berfungsi dan menghasilkan dua perjalanan pergi ke pangkalan data . Perjalanan pergi balik pertama memilih pengguna, dan yang kedua memilih dokumen mereka.

3.3. Pemuatan Malas Di Luar Transaksi

Sekarang, mari kita panggil kaedah bukan transaksi untuk mensimulasikan ralat yang kita dapat tanpa transaksi di sekitar:

@Test(expected = LazyInitializationException.class) public void whenCallNonTransactionalMethodWithPropertyOff_thenThrowException() { serviceLayer.countAllDocsNonTransactional(); }

Seperti yang diramalkan, ini menghasilkan kesalahan kerana fungsi getDocs Pengguna digunakan di luar transaksi.

3.4. Pemuatan Malas Dengan Transaksi Automatik

Untuk memperbaikinya, kita boleh mengaktifkan harta tanah:

spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true

Dengan harta dihidupkan, kami tidak lagi mendapat LazyInitializationException .

Walau bagaimanapun, jumlah pertanyaan menunjukkan bahawa enam perjalanan telah dibuat ke pangkalan data . Di sini, satu perjalanan pergi balik memilih pengguna, dan lima perjalanan pergi balik memilih dokumen untuk setiap lima pengguna:

@Test public void whenCallNonTransactionalMethodWithPropertyOn_thenGetNplusOne() { SQLStatementCountValidator.reset(); long docsCount = serviceLayer.countAllDocsNonTransactional(); assertEquals(EXPECTED_DOCS_COLLECTION_SIZE, docsCount); SQLStatementCountValidator.assertSelectCount(EXPECTED_USERS_COUNT + 1); }

Kami menghadapi masalah N + 1 yang terkenal , walaupun pada hakikatnya kami menetapkan strategi pengambilan untuk mengelakkannya!

4. Membandingkan Pendekatan

Mari kita bincangkan secara ringkas kebaikan dan keburukan.

Dengan harta dihidupkan, kita tidak perlu bimbang tentang urus niaga dan sempadannya. Hibernate menguruskannya untuk kita.

Namun, penyelesaiannya berjalan perlahan, kerana Hibernate memulakan transaksi untuk kami pada setiap pengambilan.

Ia berfungsi dengan sempurna untuk demo dan ketika kita tidak peduli dengan masalah prestasi. Ini mungkin baik jika digunakan untuk mengambil koleksi yang hanya mengandungi satu elemen, atau satu objek yang berkaitan dalam hubungan satu lawan satu.

Tanpa harta tanah, kami mempunyai kawalan transaksi yang terperinci, dan kami tidak lagi menghadapi masalah prestasi.

Secara keseluruhan, ini bukan ciri siap produksi , dan dokumentasi Hibernate memberi amaran kepada kami:

Walaupun mengaktifkan konfigurasi ini dapat membuat LazyInitializationException hilang, lebih baik menggunakan rancangan pengambilan yang menjamin bahawa semua sifat dimulakan dengan betul sebelum Sesi ditutup.

5. Kesimpulan

Dalam tutorial ini, kami meneroka menangani pemuatan yang malas.

Kami mencuba harta Hibernate untuk membantu mengatasi LazyInitializationException . Kami juga melihat bagaimana ia mengurangkan kecekapan dan mungkin hanya merupakan penyelesaian yang sesuai untuk sebilangan kes penggunaan yang terhad.

Seperti biasa, semua contoh kod boleh didapati di GitHub.