Hibernate는 프록시를 초기화 할 수 없습니다 – 세션 없음

1. 개요

Hibernate로 작업하면서, org.hibernate.LazyInitializationException : could not initialize proxy – no Session 이라는 오류가 발생했을 수 있습니다 .

이 빠른 자습서에서는 오류의 근본 원인을 자세히 살펴보고이를 방지하는 방법을 알아 봅니다.

2 오류 이해

열린 Hibernate 세션의 컨텍스트 외부에서 지연로드 된 객체에 액세스하면이 예외가 발생합니다.

Session , Lazy Initialisation Proxy Object 가 무엇인지 , 그리고 이들이 Hibernate 프레임 워크 에서 어떻게 결합되는지 이해 하는 것이 중요합니다 .

  • 세션 은 애플리케이션과 데이터베이스 간의 대화를 나타내는 지속성 컨텍스트입니다.
  • 지연로드 는 코드에서 액세스 할 때까지 객체가 세션 컨텍스트에 로드되지 않음을 의미합니다 .
  • Hibernate 는 우리가 객체를 처음 사용할 때만 데이터베이스를 공격 할 동적 프록시 객체 서브 클래스를 생성 합니다.

이 오류는 프록시 개체를 사용하여 데이터베이스에서 지연로드 된 개체를 가져 오려고하지만 Hibernate 세션이 이미 닫 혔음을 의미합니다.

3. LazyInitializationException의

구체적인 시나리오에서 예외를 살펴 보겠습니다.

연관된 역할이 있는 간단한 사용자 개체 를 만들고 싶습니다 . LazyInitializationException 오류 를 보여주기 위해 JUnit을 사용합시다 .

3.1. Hibernate 유틸리티 클래스

먼저, 설정을 가진 SessionFactory 를 생성하기 위해 HibernateUtil 클래스를 정의합시다 .

메모리 내 HSQLDB 데이터베이스를 사용합니다.

3.2. 엔티티

다음은 사용자 엔티티입니다.

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

그리고 연관된 역할 엔티티 :

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

보시다시피 UserRole 사이에는 일대 다 관계가 있습니다.

3.3. 역할이있는 사용자 생성

다음으로 두 개의 역할 개체를 만들어 보겠습니다 .

Role admin = new Role("Admin"); Role dba = new Role("DBA");

그런 다음 역할을 가진 사용자 를 만듭니다 .

User user = new User("Bob", "Smith"); user.addRole(admin); user.addRole(dba);

마지막으로 세션을 열고 객체를 유지할 수 있습니다.

Session session = sessionFactory.openSession(); session.beginTransaction(); user.getRoles().forEach(role -> session.save(role)); session.save(user); session.getTransaction().commit(); session.close();

3.4. 역할 가져 오기

첫 번째 시나리오에서는 적절한 방법으로 사용자 역할을 가져 오는 방법을 살펴 보겠습니다.

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

여기서는 세션 내부의 객체에 액세스하므로 오류가 없습니다.

3.5. 역할 가져 오기 실패

두 번째 시나리오에서는 세션 외부에서 getRoles 메서드를 호출합니다 .

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

이 경우 세션이 닫힌 후 역할에 액세스하려고 시도하고 결과적으로 코드에서 LazyInitializationException을 발생 시킵니다.

4. 오류를 피하는 방법

오류를 극복하기위한 네 가지 솔루션을 살펴 보겠습니다.

4.1. 상위 계층에서 세션 열기

모범 사례는 예를 들어 DAO 패턴을 사용하여 지속성 계층에서 세션을 여는 것입니다.

상위 계층에서 세션을 열어 안전한 방식으로 연결된 개체에 액세스 할 수 있습니다. 예를 들어 레이어 에서 세션을 열 수 있습니다 .

결과적으로 응답 시간이 증가하여 애플리케이션 성능에 영향을 미칩니다.

이 솔루션은 분리 원칙의 관점에서 반 패턴입니다. 또한 데이터 무결성 위반 및 장기 실행 트랜잭션을 유발할 수 있습니다.

4.2. enable_lazy_load_no_trans 속성 켜기

이 Hibernate 속성은 지연로드 된 객체 가져 오기에 대한 전역 정책을 선언하는 데 사용됩니다.

기본적으로이 속성은 false 입니다. 이 기능을 켜면 연결된 지연로드 엔터티에 대한 각 액세스가 새 트랜잭션에서 실행되는 새 세션으로 래핑됩니다.

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

이 기사에서 우리는 org.hibernate.LazyInitializationException : could not initialize proxy – no Session error 를 처리하는 방법을 보았습니다 .

성능 문제와 함께 다양한 접근 방식을 탐색했습니다. 성능에 영향을주지 않도록 간단하고 효율적인 솔루션을 사용하는 것이 중요합니다.

마지막으로 Join-fetching 접근 방식이 오류를 피하는 좋은 방법임을 보았습니다.

항상 그렇듯이 코드는 GitHub에서 사용할 수 있습니다.