Hibernate tidak dapat memulakan proksi - tanpa Sesi

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.