DEV/Spring

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

Bi3a 2024. 1. 4. 11:38

목차
반응형

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


 

 

문제 상황

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

 

[MemberService]

java
닫기
@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]

java
닫기
@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

java
닫기
@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

java
닫기
@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이 작동하지 않는다.

 


 

반응형