DEV/Spring

[트러블슈팅][Spring] @Transactional 미작동 시 해결방법

Bi3a 2024. 1. 4. 11:38

728x90

트러블슈팅
안되는 이유는 모르겠고 되는 이유는 더더욱 모르겠을 때


 

 

문제 상황

분명히 @Transactional을 명시하였고, Jpa Repository로 save까지 함에도
세터로 인한 멤버 엔티티의 refreshToken이 DB로 업데이트되지 않는 문제가 발생했다.

 

[MemberService]

@Transactional(readOnly = true)
public class MemberService {

	// MemberService 로직 중 일부
    public void setupTokenWhenLogin(Member member) {
        String accessToken = makeToken(member, 5);
        String refreshToken = makeToken(member, 60 * 24 * 7);
        setRefreshToken(member, refreshToken);

        rq.setAccessTokenToCookie(accessToken);
        rq.setRefreshTokenToCookie(refreshToken);
    }
    
    @Transactional
    public void setRefreshToken(Member member, String refreshToken) {
        member.setRefreshToken(refreshToken); // 문제가 되었던 부분 1
        memberRepository.save(member);
    }
}

 

[Member]

    @Entity
    public class Member {
        
        // member 엔티티 로직 중 일부
        @Setter
        @Column(columnDefinition = "TEXT")
        private String refreshToken;
    }

 

해결 방법

@TransActional / JPA에 의한 더티체킹은 가장 외부에 있는 메서드의 @Transactional을 기준으로 작동한다.
즉, 내부 메소드에 @Transactional이 명시되어 있어도 작동하지 않는다면,
가장 외부 메소드에 @Transactional이 명시되어 있는지 확인하자.

 

해결방법 1) 외부 메서드에 @Transactional을 명시

스프링의 @Transactional 은 인스턴스에서 처음으로 호출하는 메서드나 클래스의 속성을 따라가게 된다.

setRefreshToken은 @Transactional을 명시했지만, 이를 호출하는 부모 레벨의 메서드인 setupTokenWhenLogin 메서드는 클래스 레벨에서의 트랜잭션 정책인 @Transactional이 readonly = true를 적용하기 때문에 발생하는 문제였다.

즉, 동일한 Bean안에 상위 메서드가 @Transactional이 없으면 하위 메소드의 선언에도 전이되지 않는다.

 

MemberService

@Transactional(readOnly = true)
public class MemberService {

    @Transactional // 추가한 부분
    public void setupTokenWhenLogin(Member member) {
        String accessToken = makeToken(member, 5);
        String refreshToken = makeToken(member, 60 * 24 * 7);
        // 자식(내부) 메소드에 @Transactional이 명시되어 있으므로 외부(부모) 메소드도 명시 필요
        setRefreshToken(member, refreshToken); 

        rq.setAccessTokenToCookie(accessToken);
        rq.setRefreshTokenToCookie(refreshToken);
    }
    // 이하 생략
}

 

해결방법 2) 기존 메소드 Transactional 전파 정책 변경

또 다른 방법은 자식 메소드의 트랜잭션 전파 정책을 변경하는 것이다.

@Transactional에 Propagation.REQUIRES_NEW를 명시하면
부모 메소드의메서드의 트랜잭션과 독립적으로 새로운 트랜잭션을 시작하며, 부모 메서드의 영향을 받지 않는다.

 

MemberService

@Transactional(readOnly = true)
public class MemberService {

    public void setupTokenWhenLogin(Member member) {
		// .. 생략
    }
    
    @Transactional(propagation = Propagation.REQUIRES_NEW) // 수정된 부분
    public void setRefreshToken(Member member, String refreshToken) {
        member.setRefreshToken(refreshToken); 
        memberRepository.save(member);
    }
}

 

결론

  • 동일한 Bean에서 호출하는 외부 메소드와 내부 메서드가 모두 @Transactional을 명시하였을 때 이는 외부 메서드의 @Transactional 정책을 적용받는다. (내부 메서드의 @Transactional 정책은 적용 X)
    • @Transactional 은 인스턴스에서 처음으로 호출하는 메서드나 클래스의 속성을 따라가기 때문이다.
  • 의도한 바대로 @Transactional이 동작하지 않는 경우에는 외부 메소드, 내부 메서드를 모두 확인하자.
  • (+ 추가) private 메소드의 경우에도 @Transactional이 작동하지 않는다.