• 카테고리

    질문 & 답변
  • 세부 분야

    백엔드

  • 해결 여부

    미해결

LazyInitializationException 에러 관련 질문 드립니다.

21.04.09 10:51 작성 조회수 462

0

안녕하세요. LazyInitializationException 예외 관련 질문 드립니다.

우선 Employee 와 Company 라는 엔티티가 N:1 관계로 셋팅 되어 있고(ManyToOne으로 설정 했고 조인 컬럼도 명시 했으며, OneToMany쪽에는 mappedBy도 맞게 설정 하였습니다. 양쪽 다 모두 Lazy로딩으로 해놨구요), JpaRepository도 각각 적절히 셋팅되어 있으며, 다음과 같은 서비스 클래스가 있다고 가정했을 때,

@Service

class CompanyService {

  @Autowired

  EmployeeRepository employeeRepository; // JpaRepository를 상속한 인터페이스

  

   @Transactional

   public test() {

    Employee employee = employeeRepository.findById(1L); // LazyInitializationException 예외 발생

    Company company = employee.getCompany();  //  could not initialize proxy - no session 예외 발생

 }

}

Employee employee = employeeRepository. findById(1L) 을 호출 하면, 디버거에서 보이는 employee 객체 내의 company 값은 실제 객체 대신 다음과 같은 예외가 보입니다.

method threw 'org.hibernate.LazyInitializationException' exception. Can not evaluate com....$HibernateProxy$lfgdgjt.toSting()

그리고 employee의 getCompany를 호출 하는 순간, could not initialize proxy - no session 이라는 예외가 발생합니다.

지연 로딩 시 영속성이 유지 되어야 하지만 findById 의 호출이 끝나는 순간 트랜잭션이 종료 되고 세션이 닫히는 게 이유가 아닐까 싶어, 트랜잭션 어노테이션을 서비스 레이어의 메소드에 추가도 해보고 전파 속성도 여러가지로 바꾸어 봤지만 문제가 해결되지 않았구요..

EAGER 로딩으로 바꾸 거나, 아래 속성을 줄 경우에 해결이 되었습니다.. 

enable_lazy_load_no_trans=true

enable_lazy_load_no_trans속성이 자칫 N+1 문제를 야기할 수 있어 안티 패턴인 것 같아 근본 원인을 알고 싶은데요..

물론 페치 조인으로도 해결 할 수 있지만, 위의 예제 코드도 당연히 동작을 해야 할 것 같은데 왜 트랜젝션 어노테이션을 주었음에도 영속성 세션이 test() 메소드 내에서 지속 되지 않는지 궁금합니다.

---

추가로 트랜잭션 로그를 찍어 보았는데 이벤트 순서가 아래와 같습니다..

test() 메소드의 트랜잭션 생성, EntityManager 열림

=> findById() 가 호출

=> SimpleJpaRepository의 inner transaction이 생성 및 새로운 EntityManager열림

=> findById 메소드 호출 종료

=> commit & inner transaction 종료

=> EntityManager 닫힘

=> test()메소드의 트랜잭션 resume 

결국 inner트랜잭션이 별도로 생성되는게 문제인 것 같은데 이게 트랜잭션 propagation을 REQUIRE로 해도 각각 별도의 트랜잭션을 생성하고 있습니다. 어떤 부분을 더 의심하고 디버깅 해봐야 할까요?

답변 1

답변을 작성해보세요.

0

안녕하세요. Woo-seok Choi님

CompanyService의 전체 코드를 보여주시겠어요?

안녕하세요. 일단 해결 했습니다. 원인은 멀티 소스를 사용하는데 secondary로 추가한 데이터소스의 엔티티매니저와 트랜잭션매니저를 스프링이 제대로 찾지 못하는 것이었구요. (기존에 원래 있던 데이터소스가 @Primary로 지정되어 있고 제가 추가한 것은 @Primary가 붙어 있지 않았습니다.)

@Transactional("트랜잭션 매니저 빈 이름") 와 같이 트랜잭션 어노테이션에 빈 이름을 추가하니 이제 SimpleJpaRepository의 트랜잭션이 서비스 메소드의 트랜잭션에 참여 하고 지연로딩도 잘 되는걸 확인 했습니다.

여기서 한 가지 궁금한 점이 생기는데요.. 현재는 서비스 메소드에만 @Transactional("트랜잭션 매니저 빈 이름") 어노테이션을 붙인 상태인데요.

JpaRepository를 상속 받는 제가 만든 EmployeeRepository 에도 @Transactional("트랜잭션 매니저 빈 이름") 을 붙여서 primary로 지정된 매니저가 아닌 제가 추가한 트랜잭션 매니저를 쓰겠다고 명시 해야 할까요? 그렇지 않으면 SimpleJpaRepository에서 디폴트로 지정된 트랜잭션 어노테이션이 제가 추가한 트랜잭션 매니저가 아닌 기존에 @Primary로 지정된 트랜잭션 매니저를 쓰는게 아닌가 생각되어서요.

사실 EmployeeRepository는 트랜잭션 어노테이션 없이도 잘 동작 하는데 혹시나 하여 여쭤 봅니다.

안녕하세요. Woo-seok Choi님 아마도 데이터 JPA를 사용하셨으면 @EnableJpaRepositories에 어떤 트랜잭션을 사용하는지 다 설정해두셨을거에요^^ 이게 서비스에 설정한 트랜잭션과 딱 맞아떨어져야 합니다. 그렇지 않으면 트랜잭션을 2개 사용하는 상황이 발생할 수 있습니다.