Spring Data JPA Transaction Propagation, EntityManager, PersistContext에 관한 고찰

문법식·2022년 10월 29일
1

Thread starvation or clock leap detected 트러블슈팅 링크


이 글을 작성하게 된 이유는 친구와 면접 준비를 하다가 내가 트러블슈팅한 글(위의 링크)에 대해 질문을 받았을 때 생긴 의문 때문이다.

의문은 다음과 같다.

  • @Transactionaldefault propagtaionREQUIRED이다. 이 경우 부모 트랜잭션이 있으면 자식 트랜잭션은 생성하지 않고 부모 트랙잭션에 participate한다. 다시 말하자면 다음과 같다. 트러블슈팅한 글에서 Service 메소드가 호출될 때 Service의 트랜잭션이 생성된다. 하지만 Repository의 메소드가 호출될 때 Repository의 트랜잭션은 생성되지 않고 Service에서 생성한 트랜잭션에 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는 전부 JPARepositoryextend한다. 그 구현체인 SimpleJPARepository도 엔티티 매니저를 주입받고 있다. 즉, 내가 트러블슈팅을 겪은 코드와 예제 코드 13.1이 똑같은 상황이다. 정리하면 다음과 같다.

  • Service의 트랜잭션에 서로 다른 2개의 Repositoryparticipate하고 각각 엔티티 매니저를 가지고 있다.
  • 엔티티 매니저는 데이터베이스 연결이 필요한 시점에 Connection을 얻는다. 즉, 한 트랜잭션에 2개의 Connection이 필요한 상황이다.

글 위에 첨부한 트러블슈팅은 원인은 틀렸지만,Connection이 부족하게 되는 상황과 그 해결법은 여전히 똑같다. 글 위에 첨부한 트러블슈팅에서 해결법을 확인하면 된다.

profile
백엔드

0개의 댓글