작성
·
606
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로 해도 각각 별도의 트랜잭션을 생성하고 있습니다. 어떤 부분을 더 의심하고 디버깅 해봐야 할까요?
안녕하세요. 일단 해결 했습니다. 원인은 멀티 소스를 사용하는데 secondary로 추가한 데이터소스의 엔티티매니저와 트랜잭션매니저를 스프링이 제대로 찾지 못하는 것이었구요. (기존에 원래 있던 데이터소스가 @Primary로 지정되어 있고 제가 추가한 것은 @Primary가 붙어 있지 않았습니다.)
@Transactional("트랜잭션 매니저 빈 이름") 와 같이 트랜잭션 어노테이션에 빈 이름을 추가하니 이제 SimpleJpaRepository의 트랜잭션이 서비스 메소드의 트랜잭션에 참여 하고 지연로딩도 잘 되는걸 확인 했습니다.
여기서 한 가지 궁금한 점이 생기는데요.. 현재는 서비스 메소드에만 @Transactional("트랜잭션 매니저 빈 이름") 어노테이션을 붙인 상태인데요.
JpaRepository를 상속 받는 제가 만든 EmployeeRepository 에도 @Transactional("트랜잭션 매니저 빈 이름") 을 붙여서 primary로 지정된 매니저가 아닌 제가 추가한 트랜잭션 매니저를 쓰겠다고 명시 해야 할까요? 그렇지 않으면 SimpleJpaRepository에서 디폴트로 지정된 트랜잭션 어노테이션이 제가 추가한 트랜잭션 매니저가 아닌 기존에 @Primary로 지정된 트랜잭션 매니저를 쓰는게 아닌가 생각되어서요.
사실 EmployeeRepository는 트랜잭션 어노테이션 없이도 잘 동작 하는데 혹시나 하여 여쭤 봅니다.