Jpa 테스트 시 TransactionRequiredException 발생과 해결
배경
조회 수 증가 로직을 개발하고 동시성 문제와 성능을 측정하기 위해 멀티 쓰레드 기반으로 테스트를 진행했다.
이때 내가 작성한 쿼리를 실행하니 TransactionRequiredException 문제가 발생했고, 이를 해결한 과정을 공유하려고 한다.
먼저 조회 수 증가에서 동시성 문제를 해결하기 위해 다양한 방법이 있지만, 나는 원자성 쿼리를 통한 비관적 락을 사용했다.
원자성 쿼리를 사용한 비관적 락 적용 시 낙관적 락과 비교하여 성능 테스트 결과 차이가 없다라는 결론을 얻었기 때문이다.

따라서 이와 같은 원자성 쿼리를 작성했다.
문제
int numberOfThreads = 100;
ExecutorService executorService = Executors.newFixedThreadPool(10);
CountDownLatch latch = new CountDownLatch(numberOfThreads);
for (int i = 0; i < numberOfThreads; i++) {
executorService.submit(() -> {
studyRepository.increaseViewCount(savedStudy.getId());
return null;
});
}
latch.await();
executorService.shutdown();
이와 같이 10개의 쓰레드를 통해 총 100번의 좋아요 요청을 보내고, 제대로 좋아요가 찍히는지 테스트를 진행했다.
그런데 이때 문제가 발생했다.
바로 TransactionRequiredException
일단 에러 메시지 그대로 트랜잭션이 필요하다는 것은 바로 알 수 있었다.
기본적으로 JPA는 트랜잭션 안에서만 작동할 수 있다는 것은 알고 있었다.
다만, Spring Data Jpa는 트랜잭션을 달지 않아도 없으면 Transaction을 만들어 제공하고, Transaction이 넘어오면 그걸 사용한다고 알고 있었는데??
혹시나 해서 @Transaction이 붙은 service를 통해 호출하면 테스트가 무리 없이 통과 되었다.
일단은 해당 쿼리가 Transaction내에서 실행되지 않고 있다는 것을 알게 되었다
해결
정확히 정보를 알아보기 위해 Spring Data Jpa 공식 문서를 둘러보았다.
그 결과 Spring Data Jpa가 Transaction을 제공하는 경우는 Spring Data Jpa가 제공하는 CrudRepository 메서드를 사용할 때만 Transaction을 자동으로 제공받을 수 있다.

findById, save 등 기본으로 제공하는 메서드에 한해서만 Transaction을 제공 받으며, 만약 내가 직접 메서드를 선언하거나, 또는 기본 제공 메서드를 오버라이딩 할 경우 Transaction을 자동으로 제공해주지 않는다고 한다.
따라서 내 테스트는 쿼리를 직접 만든 상황이기에 Transaction을 제공 받지 못하고 있던 것이다!
이를 해결하기 위해 TransactionTeamplate을 통해 템플릿 메서드 콜백 패턴으로 내가 테스트 하는 코드를 콜백으로 넘기며 트랜잭션을 적용해줘서 해결했다.
