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
이 부족하게 되는 상황과 그 해결법은 여전히 똑같다. 글 위에 첨부한 트러블슈팅에서 해결법을 확인하면 된다.