1. 개요

Hibernate로 작업할 때 org.hibernate.LazyInitializationException: can not initialize proxy – no Session 이라는 오류가 발생했을 수 있습니다 .

이 빠른 사용방법(예제)에서는 오류의 근본 원인을 자세히 살펴보고 오류를 방지하는 방법을 배웁니다.

2  오류 이해

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

Session , Lazy Initialization Proxy Object 가 무엇이며 Hibernate 프레임워크 에서 이들이 어떻게 함께 왔는지 이해 하는 것이 중요합니다 .

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

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

3. LazyInitializationException의

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

연결된 역할이 있는 간단한 사용자 개체 를 만들고 싶습니다 . JUnit을 사용하여 LazyInitializationException 오류 를 보여줍시다 .

3.1. 최대 절전 모드 유틸리티 클래스

먼저 HibernateUtil 클래스를 정의하여 구성이 있는 SessionFactory 를 생성 합시다 .

메모리 내 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<Role> 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 객체를 생성해 보겠습니다 .

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가지 다른 솔루션을 살펴보겠습니다.

4.1. 상위 레이어에서 세션 열기

가장 좋은 방법은 예를 들어 DAO 패턴을 사용하여 지속성 계층에서 세션을 여는 것 입니다.

상위 계층에서 세션을 열어 연결된 개체에 안전하게 액세스할 수 있습니다. 예를 들어 View 계층 에서 세션을 열 수 있습니다 .

결과적으로 응답 시간이 늘어나 애플리케이션 성능에 영향을 미칩니다.

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

4.2. enable_lazy_load_no_trans 속성 켜기

이 Hibernate 속성은 지연 로드된 객체 페칭을 위한 전역 정책을 선언하는 데 사용됩니다.

기본적으로 이 속성은 false 입니다. 켜는 것은 관련 지연 로드 엔터티에 대한 각 액세스가 새 트랜잭션에서 실행되는 새 세션에서 래핑됨을 의미합니다.

<property name="hibernate.enable_lazy_load_no_trans" value="true"/>

이 속성을 사용하여 LazyInitializationException 오류 를 방지 하는 것은 응용 프로그램의 성능을 저하 시키므로 권장되지 않습니다. 이것은 n+1 문제로  끝날 것이기 때문 입니다. 간단히 말해서 사용자에 대한 하나의 SELECT 와 각 사용자의 역할을 가져오기 위한 N개의 추가 SELECT를 의미합니다.

이 접근 방식은 비효율적이며 반패턴으로도 간주됩니다.

4.3. FetchType.EAGER 전략 사용  

@OneToMany 어노테이션 과 함께 이 전략을 사용할 수 있습니다  . 예를 들면 다음과 같습니다.

@OneToMany(fetch = FetchType.EAGER)
@JoinColumn(name = "user_id")
private Set<Role> roles;

이것은 대부분의 사용 사례에 대해 관련 컬렉션을 가져와야 할 때 특정 용도에 대한 일종의 손상된 솔루션입니다.

따라서 대부분의 다양한 비즈니스 흐름에 대해 컬렉션을 명시적으로 가져오는 대신 EAGER 가져오기 유형 을 선언하는 것이 훨씬 쉽습니다 .

4.4. 조인 가져오기 사용

JPQL 에서 JOIN FETCH 지시문을 사용하여 요청 시 관련 컬렉션을 가져올 수 있습니다. 예를 들면 다음과 같습니다.

SELECT u FROM User u JOIN FETCH u.roles

또는 Hibernate Criteria API를 사용할 수 있습니다.

Criteria criteria = session.createCriteria(User.class);
criteria.setFetchMode("roles", FetchMode.EAGER);

여기 에서 동일한 왕복에 대해 사용자 개체 와 함께 데이터베이스에서 가져와야 하는 관련 컬렉션을 지정합니다 . 이 쿼리를 사용하면 연결된 개체를 별도로 검색할 필요가 없으므로 반복의 효율성이 향상됩니다.

이것은 LazyInitializationException 오류 를 피하기 위한 가장 효율적이고 세분화된 솔루션 입니다.

5. 결론

이 기사에서 우리는 org.hibernate.LazyInitializationException 을 처리하는 방법을 보았습니다 . 프록시를 초기화할 수 없습니다 – 세션 오류가 없습니다 .

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

마지막으로 조인 페칭 접근 방식이 오류를 방지하는 좋은 방법임을 확인했습니다.

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

Persistence footer banner