1. Gambaran keseluruhan
Bekerja dengan Hibernate, kami mungkin mengalami ralat yang mengatakan: org.hibernate.LazyInitializationException: tidak dapat memulakan proksi - tanpa Sesi .
Dalam tutorial ringkas ini, kita akan melihat lebih dekat punca ralat dan belajar bagaimana menghindarinya.
2 Memahami Kesalahan
Akses ke objek yang dimuatkan malas di luar konteks sesi Hibernate terbuka akan menghasilkan pengecualian ini.
Penting untuk memahami apa itu Sesi , Permulaan Malas, dan Objek Proksi dan bagaimana mereka bersatu dalam kerangka Hibernate .
- Sesi adalah konteks kegigihan yang mewakili perbualan antara aplikasi dan pangkalan data
- Pemuatan Malas bermaksud bahawa objek tidak akan dimuat ke konteks Sesi sehingga ia diakses dalam kod.
- Hibernate membuat subkelas Objek Proksi dinamik yang akan memukul pangkalan data hanya ketika kami pertama kali menggunakan objek.
Kesalahan ini bermaksud bahawa kita cuba mengambil objek yang dimuatkan malas dari pangkalan data dengan menggunakan objek proksi, tetapi sesi Hibernate sudah ditutup.
3. Contoh untuk LazyInitializationException
Mari lihat pengecualian dalam senario konkrit.
Kami ingin membuat objek Pengguna ringkas dengan peranan yang berkaitan. Mari gunakan JUnit untuk menunjukkan kesilapan LazyInitializationException .
3.1. Kelas Utiliti Hibernate
Pertama, mari tentukan kelas HibernateUtil untuk membuat SessionFactory dengan konfigurasi.
Kami akan menggunakan pangkalan data HSQLDB dalam memori .
3.2. Entiti
Inilah entiti Pengguna kami :
@Entity @Table(name = "user") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") private int id; @Column(name = "first_name") private String firstName; @Column(name = "last_name") private String lastName; @OneToMany private Set roles; }
Dan entiti Peranan yang berkaitan :
@Entity @Table(name = "role") public class Role { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") private int id; @Column(name = "role_name") private String roleName; }
Seperti yang dapat kita lihat, terdapat hubungan satu-ke-banyak antara Pengguna dan Peranan .
3.3. Membuat Pengguna dengan Peranan
Seterusnya, mari buat dua objek Peranan :
Role admin = new Role("Admin"); Role dba = new Role("DBA");
Kemudian, kami membuat Pengguna dengan peranan:
User user = new User("Bob", "Smith"); user.addRole(admin); user.addRole(dba);
Akhirnya, kita dapat membuka sesi dan meneruskan objek:
Session session = sessionFactory.openSession(); session.beginTransaction(); user.getRoles().forEach(role -> session.save(role)); session.save(user); session.getTransaction().commit(); session.close();
3.4. Mengambil Peranan
Dalam senario pertama, kita akan melihat cara mengambil peranan pengguna dengan cara yang betul:
@Test public void whenAccessUserRolesInsideSession_thenSuccess() { User detachedUser = createUserWithRoles(); Session session = sessionFactory.openSession(); session.beginTransaction(); User persistentUser = session.find(User.class, detachedUser.getId()); Assert.assertEquals(2, persistentUser.getRoles().size()); session.getTransaction().commit(); session.close(); }
Di sini, kami mengakses objek di dalam sesi, oleh itu tidak ada kesalahan.
3.5. Mengambil Kegagalan Peranan
Dalam senario kedua, kami akan memanggil kaedah getRoles di luar sesi:
@Test public void whenAccessUserRolesOutsideSession_thenThrownException() { User detachedUser = createUserWithRoles(); Session session = sessionFactory.openSession(); session.beginTransaction(); User persistentUser = session.find(User.class, detachedUser.getId()); session.getTransaction().commit(); session.close(); thrown.expect(LazyInitializationException.class); System.out.println(persistentUser.getRoles().size()); }
Dalam kes itu, kami cuba mengakses peranan setelah sesi ditutup, dan, sebagai hasilnya, kod tersebut melemparkan LazyInitializationException .
4. Cara Mengelakkan Kesalahan
Mari kita lihat empat penyelesaian yang berbeza untuk mengatasi kesalahan tersebut.
4.1. Sesi Terbuka di Lapisan Atas
Amalan terbaik adalah membuka sesi di lapisan ketekunan, misalnya menggunakan Pola DAO.
Kita boleh membuka sesi di lapisan atas untuk mengakses objek yang berkaitan dengan cara yang selamat. Sebagai contoh, kita dapat membuka sesi di lapisan View .
Hasilnya, kita akan melihat peningkatan masa tindak balas, yang akan mempengaruhi prestasi aplikasi.
Penyelesaian ini adalah anti-pola dari segi prinsip Pemisahan Keprihatinan. Di samping itu, ia boleh menyebabkan pelanggaran integriti data dan transaksi yang berjalan lama.
4.2. Menghidupkan enable_lazy_load_no_trans Hartanah
Properti Hibernate ini digunakan untuk menyatakan dasar global untuk pengambilan objek dengan pemalas.
Secara lalai, harta ini salah . Menghidupkannya bermaksud bahawa setiap akses ke entiti pemalas yang berkaitan akan dibungkus dalam sesi baru yang dijalankan dalam transaksi baru:
Using this property to avoid LazyInitializationException error is not recommended since it will slow down the performance of our application. This is because we'll end up with an n + 1 problem. Simply put, that means one SELECT for the User and N additional SELECTs to fetch the roles of each user.
This approach is not efficient and also considered an anti-pattern.
4.3. Using FetchType.EAGER Strategy
We can use this strategy along with a @OneToMany annotation, for example :
@OneToMany(fetch = FetchType.EAGER) @JoinColumn(name = "user_id") private Set roles;
This is a kind of compromised solution for a particular usage when we need to fetch the associated collection for most of our use cases.
So it's much easier to declare the EAGER fetch type instead of explicitly fetching the collection for most of the different business flows.
4.4. Using Join Fetching
We can use a JOIN FETCH directive in JPQL to fetch the associated collection on-demand, for example :
SELECT u FROM User u JOIN FETCH u.roles
Or we can use the Hibernate Criteria API :
Criteria criteria = session.createCriteria(User.class); criteria.setFetchMode("roles", FetchMode.EAGER);
Here, we specify the associated collection that should be fetched from the database along with the User object on the same round trip. Using this query improves the efficiency of iteration since it eliminates the need for retrieving the associated objects separately.
This is the most efficient and fine-grained solution to avoid the LazyInitializationException error.
5. Conclusion
Dalam artikel ini, kami melihat cara menangani org.hibernate.LazyInitializationException: tidak dapat memulakan proksi - tidak ada ralat Sesi .
Kami meneroka pelbagai pendekatan bersama dengan masalah prestasi. Penting untuk menggunakan penyelesaian yang mudah dan berkesan untuk mengelakkan mempengaruhi prestasi.
Akhirnya, Kami melihat bagaimana pendekatan pengambilan bersama adalah cara yang baik untuk mengelakkan kesilapan.
Seperti biasa, kodnya tersedia di GitHub.