trouble shooting

[스프링부트] No EntityManager with actual transaction available for current thread - cannot reliably process 'remove' call

설기똥꼬 2023. 9. 15. 00:33

오류 내용

Quizeloper 좋아요 기능을 만드는 중, 제목과 같은 에러가 발생했다.

엔티티매니저가 없어 삭제 요청을 수행하지 못한다니..!

 

에러가 발생한 Service 코드는 다음과 같다.

 

public void postQuizLikes(Long userId, Long quizId){
        User user = userRepository.findByIdAndStatus(userId, ACTIVE).orElseThrow(() -> new BaseException(BaseResponseStatus.USER_NOT_FOUND));
        Quiz quiz = quizRepository.findByIdAndStatus(quizId, ACTIVE).orElseThrow(() -> new BaseException(BaseResponseStatus.QUIZ_NOT_FOUND));
        if (quizLikeRepository.existsIdByUserIdAndQuizId(user.getId(), quizId)) {
            quizLikeRepository.deleteByUserIdAndQuizId(userId, quizId);
        }
        else {
            QuizLike quizLike = QuizLike.builder().user(user).quiz(quiz).build();
            quizLikeRepository.save(quizLike);
        }

    }

유저와 퀴즈를 조회한 후, 좋아요 DB에 해당 유저와 퀴즈가 존재하는지에 따라 좋아요, 또는 좋아요 취소를 할 수 있다.

quizLikeRepository.deleteByUserIdAndQuizId(userId, quizId); 부분에서 에러가 발생했다.

 

 

발생 원인

확인해보니 삭제 메서드 수행시 삭제가 제대로 되지 않은 것이다.

 

DeleteBy_Id 동작 방식

1. EntityManager opened
2. SELECT 쿼리 생성
3. EntityManager closed
4. DELETE 쿼리 예외 발생

 

기본적으로 JPA는 트랜잭션을 기반으로 작동하게 되어 있는데, 트랜잭션 단위에 따라 1차 캐시 영역에 있는 객체들이 db에 flush되어 영속화 되기 때문이다.

 

영속작업을 하는 persist() 메소드에 객체가 들어갔으나 가능한 트랜잭션이 존재하지 않았기에 본 에러가 발생한 것이다.

 

 

해결 방법

서비스 혹은 클래스 위에 @Transactional을 선언해둠으로써 해결할 수 있었다.

 

그럼 지금까지의 Service 메서드들 중 @Transactional을 선언하지 않고도 어떻게 외부 호출이 가능했을까?

바로 JpaRepository의 구현체인 SimpleJpaRepository에서 이미 메서드에 @Transactional을 붙여 놓아서이다.

 

그래서 JpaRepository에 미리 선언된 메서드를 호출할 경우에, 서비스 클래스에서 @Transactional을 붙이지 않아도 문제가 없었던 것이다. 따라서 레포지토리 클래스에 추가로 정의한 메서드에 @Transactional을 붙여줘도 문제를 해결할 수 있다.

 

정리 내용

  • @Transactional 어노테이션을 통해 스프링에서 트랜잭션 관리를 단순화할 수 있다.
    • 데이터 일관성과 에러 처리가 간편하게 관리된다.
  • 트랜잭션은 어노테이션 기반 AOP를 통해 구현되어 @Transactional이 선언되면 해당 클래스에 트랜잭션이 적용된 프록시 객체가 생성된다.
  • 프록시 객체는 @Transactional이 포함된 메서드가 호출될 경우 트랜잭션을 시작하고 Commit이나 Rollback을 수행한다.
  • CheckedException이나 예외가 없을 때 Commit, UncheckedException이 발생하면 Rollback된다.
    • 디버깅을 통해 Commit, Rollback 과정을 바탕으로 예외 처리를 진행해야겠다.
  • JPA에서 단일 작업에 대해서는 @Transactional을 직접 선언할 필요가 없다.
    • 여러 작업을 하나의 단위로 묶어 Commit이나 Rollback 처리가 필요할 때 직접 선언한다!