작성
·
652
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....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는 트랜잭션 어노테이션 없이도 잘 동작 하는데 혹시나 하여 여쭤 봅니다.