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이 작동하지 않는다.
'DEV > Spring' 카테고리의 다른 글
[Spring] JPA mappedBy에 의한 연관관계 주인이 트랜잭션 권한을 가지는 예시 (0) | 2024.01.16 |
---|---|
[Spring Data JPA] 1 : N, N : 1 관계의 양방향 매핑과 단방향 매핑 (1) | 2024.01.16 |
[트러블슈팅][Spring] CORS 에러 해결, CORS 설정 (0) | 2023.12.30 |
[Spring Boot] MultipartFile 기반 이미지 저장 / 업로드 기능 구현 (1) | 2023.12.20 |
[Spring] @Configuration을 활용한 @Bean 등록 방법 (0) | 2023.11.23 |