Thread starvation or clock leap detected트러블슈팅 링크
이 글을 작성하게 된 이유는 친구와 면접 준비를 하다가 내가 트러블슈팅한 글(위의 링크)에 대해 질문을 받았을 때 생긴 의문 때문이다.
의문은 다음과 같다.
@Transactional의 default propagtaion은 REQUIRED이다. 이 경우 부모 트랜잭션이 있으면 자식 트랜잭션은 생성하지 않고 부모 트랙잭션에 participate한다. 다시 말하자면 다음과 같다. 트러블슈팅한 글에서 Service 메소드가 호출될 때 Service의 트랜잭션이 생성된다. 하지만 Repository의 메소드가 호출될 때 Repository의 트랜잭션은 생성되지 않고 Service에서 생성한 트랜잭션에 participate한다는 것이다.
참고
- https://junhyunny.github.io/spring-boot/jpa/junit/transactional-propagation-type/#31-%EB%B6%80%EB%AA%A8-%EB%A9%94%EC%86%8C%EB%93%9C-required---%EC%9E%90%EC%8B%9D-%EB%A9%94%EC%86%8C%EB%93%9C-required
위 블로그를 확인하면 부모 트랜잭션과 자식 트랜잭션의propagation이REQUIRED일 때 자식 트랜잭션은 생성되지 않고 부모 트랜잭션에participate하는 것을 확인할 수 있다.
트랜잭션이 한 개만 생성되면, 스프링 컨테이너의 기본 전략인 트랜잭션 범위의 영속성 컨텍스트 때문에 영속성 컨텍스트도 하나만 생긴다.
그러면 해당 영속성 컨텍스트를 관리하는 엔티티 매니저도 한 개인데 왜 내가 위에 첨부한 트러블슈팅을 작성했을 때 Connection이 부족해서 데드락이 발생한 것인가 하는 의문이다.
"부모 트랜잭션이 readOnly=true 자식 트랜잭션이 readOnly=false면 트랜잭션이 따로 생성되는 것인가?", 아니면 "내가 트랜잭션 propagation을 잘못 알고 있나?" 등 많이 생각하고 자료를 찾아봤다. "납득"에 대한 병적인 집착은 김영한님의 '자바 ORM 표준 JPA 프로그래밍' 책을 보고 나서 끝낼 수 있었다. 이 책의 예제 13.1의 코드와 설명이다.
@Contorller class HelloController { @Autowired HelloService helloService; public void hello() { //반환된 member 엔티티는 준영속 상태 -- 4 Member member = helloService.logic(); } }@Service class HelloService { @PersistenceContext //엔티티 매니저 주입 EntityManager em; @Autowired Repository1 repository1; @Autowired Repository2 repository2; //트랜잭션 시작 -- 1 @Transactional public void logic() { repository1.hello(); //member는 영속 상태 -- 2 Member member = repository2.findMember(); return member; } //트랜잭션 종료 -- 3 }@Repository class Repository1 { @PersistenceContext EntityManager em; public void hello() { em.xxx(); //A. 영속성 컨텍스트 접근 } }@Repository class Repository2 { @PersistenceContext EntityManager em; public Member findMember() { return em.find(Member.class, "id1"); //B. 영속성 컨텍스트 접근 } }예제 13.1에서 엔티티 매니저를 사용하는 A, B 코드는 모두 같은 트랜잭션 범위 안에 있다. 따라서 엔티티 매니저는 달라도 같은 영속성 컨텍스트를 사용한다.
Service 메소드에서 2개의 다른 Repository를 쓰고 있다. 2개의 Repository는 전부 JPARepository를 extend한다. 그 구현체인 SimpleJPARepository도 엔티티 매니저를 주입받고 있다. 즉, 내가 트러블슈팅을 겪은 코드와 예제 코드 13.1이 똑같은 상황이다. 정리하면 다음과 같다.
Service의 트랜잭션에 서로 다른 2개의 Repository가 participate하고 각각 엔티티 매니저를 가지고 있다.Connection을 얻는다. 즉, 한 트랜잭션에 2개의 Connection이 필요한 상황이다. 글 위에 첨부한 트러블슈팅은 원인은 틀렸지만,Connection이 부족하게 되는 상황과 그 해결법은 여전히 똑같다. 글 위에 첨부한 트러블슈팅에서 해결법을 확인하면 된다.