REQUIRES_NEW인데 rollback되는 이유가 궁금합니다.
428
작성한 질문수 11
@Service
@RequiredArgsConstructor
@Transactional
public class UserService {
public void createUser(CreateUserRequest request) {
Users users = firebaseUsersRepository.findUsersByFirebaseUid(request.getFirebaseUid())
.orElseThrow(() -> new BusinessException("Not Found User", HttpStatus.INTERNAL_SERVER_ERROR));
User user = User.builder()
.name(users.getDisplay_name())
.firebaseUid(request.getFirebaseUid())
.build();
userRepository.save(user);
}
}
@Component
@RequiredArgsConstructor
@Transactional(propagation = Propagation.REQUIRES_NEW)
public class BaseEntityAuditAware implements AuditorAware<User> {
private final UserRepository userRepository;
@Override
public Optional<User> getCurrentAuditor() {
try {
return userRepository.findById(ApiLogger.getRequestCallerId());
} catch (Exception e) {
return Optional.empty();
}
}
}createUser에서 userRepository.save(user)를 호출할때,
JpaAudit기능을 이용하기 위해 구현해놓은 BaseEntityAuditAware에서 유저정보를 가져온 후,
실제 쿼리를 날립니다.
이때, 전파속성이 REQUIRE_NEW이며, 발생한 모든 예외를 catch했으므로
이 함수를 호출한 부모 함수로 해당 예외가 전달되지 않을 것이기때문에 rollback이 되지 않으리라 기대했지만
실제로는 unexpectedrollbackexception이 발생하며 롤백이 되었습니다.
null을 반환하는건 문제가 아닌것이,
실제로 예외를 발생시키지 않으려고 위 코드를 아래와같이 변경하였더니 null값으로 정상적으로 insert쿼리가 날라갔습니다.
@Override
public Optional<User> getCurrentAuditor() {
Long callerId = ApiLogger.getRequestCallerId();
if (callerId == null)
return Optional.empty();
return userRepository.findById(ApiLogger.getRequestCallerId());
}어느부분이 잘못된것이며 제가 오개념을 잡고있는 부분이 어디일까요?
답변 1
0
안녕하세요. 조성락님
이 문제는 저도 잘 모르겠습니다.
관련해서 비슷한 경험을 하신 분 있으시면 답변 부탁드려요.
감사합니다.
0
@Override public Optional<User> getCurrentAuditor() {
try {
return userRepository
.findById(ApiLogger.getRequestCallerId());
} catch (Exception e) {
return Optional.empty();
}
}
위 수정전 코드에서
ApiLogger.getRequestCallerId()를 예외처리 해주지않아
findById자체에서 NULL이 들어가
org.springframework.dao.InvalidDataAccessApiUsageException
이 발생했고,
findById도 @Transactional이기때문에getCurrentAuditor
에서 롤백마킹이 된거같습니다.(추측)
그런데 궁금한점은 REQUIRES_NEW이기때문에, 여기서 롤백이 끝나야 할꺼같지만, 상위까지 롤백이 전파되었다는 점입니다.
0
여러가지 테스트 결과,
아래와 같은 흐름으로 테스트 코드를 구성해보면
A(REQUIRES) --> 호출 --> B(REQEUIRES_NEW) --> 호출 --> C(REQUIRES)
위와같은 호출 순서와, 전파속성을 가지고있을때,
B에서 발생한 Unchecked Exception에 대해해서는
B에서 try-catch 해준다면 롤백마킹이 되지 않습니다.
하지만 C에서 발생한 Unchecke Exception에 대해서는
똑같이 롤백마킹이 됩니다.
그런데 여기서 의아한점은,
TransactionSynchronizationManager.getCurrentTransactionName()을 찍어보면
C의 트랜잭션이름이 B에서 새로생성된 트랜잭션 이름이라는점입니다.
0
따라서 getCurrtentAuidtor에서는 try-catch를 해주었더라고,
getCurretnAuditor에서 호출하는 findById가 UncheckedExcpetion을 발생시켰기때문에
createUser에서도 롤백이 발생한거 같습니다,
1
https://velog.io/@sju3358/%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%EC%A0%84%ED%8C%8C%EC%86%8D%EC%84%B1-REQUIRESNEW%EC%99%80-ROLL-BACK
위현상과 관련하여 블로그에 정리해보았습니다.
2
안녕하세요. 조성락님
블로그에 정리해두신 글을 보니 어떤 문제로 고민하시는지 이해가 되네요 🙂
다음 부분을 차근차근 공부해보시면 정확히 어떤 이유로 이런 문제가 발생하는지 정답을 찾을 수 있을거에요. 핵심은 UnexpectedRollbackException이 왜 발생하는지 정확히 이해를 하셔야 합니다.
스프링 DB 2편
10. 스프링 트랜잭션 전파1 - 기본 -> 스프링 트랜잭션 전파6 - 내부 롤백
11. 스프링 트랜잭션 전파2 - 활용 -> 트랜잭션 전파 활용6 - 복구 REQUIRED
감사합니다.
0
안녕하세요
알려주신
스프링 트랜잭션 전파6 - 내부 롤백
트랜잭션 전파 활용6 - 복구 REQUIRED
에서는 서로 같은 트랜잭션안에 있을때, 논리 트랜잭션중 어느 하나라도 예외가 터지면
롤백마킹이 되어 물리 트랜잭션이 롤백이 된다는 내용인데요,
스프링 트랜잭션 전파7 - REQUIRES_NEW
트랜잭션 전파 활용7 - 복구 REQUIRES_NEW
의 경우, REQUIRES_NEW인 논리 트랜잭션이 롤백이 되더라도,
트랜잭션이 분리되어있기때문에, 이를 호출한 다른 물리트랜잭션은 커밋이 된다는 내용으로 이해했습니다.
그래서 저의 상황인
A -> B -> C
A(REQUIRED)에서 B(REQUIRES_NEW)를 호출하고
B(REQUIRES_NEW)가 C(REQUIRED)를 호출한 상황에서
C에서 RuntimeExeption이 발생하여, C,B가 롤백이 되었어도,
트랜잭션1 : A
트랜잭션2 : B,C이므로
롤백마킹이 A까지 전파되지 않아야하는것이 아닌가? 하는 의문이 해결되지 않습니다.
왜냐하면
A와 C는 다른 트랜잭션이고,
C에서 발생한 exception은 B에서 try-catch했기때문에 A까지 전달되지 않았기 때문입니다.
트랜잭션 전파 활용7 - 복구 REQUIRES_NEW
트랜잭션 전파 활용7 - 복구 REQUIRES_NEW
0
혹시 제가 이해한것이 맞을까요?
디버깅을 통해 확인한 결과
1. C에서 발생한 RuntimeException을 B에서 TRY-CATCH했고
C에서 롤백마킹을 햇으며, B에서 커밋을 시도할때, 롤백마킹이 되어있어
UnexpectedRollbackException을 발생시킴.
2. 이예외는 B가 invoke된 후 Transaction AOP에서 호출한것이기때문에 A까지 전달되됨. A에서는 UnexpectedRollBackException이라는 RuntimeException이 발생하여 rollback 됨
-----
제가 이해한것이 맞다면
왜 A(REQUIRED) --> B(REQUIRES_NEW) --> C(REQUIRES_NEW)에서는
UnexpectedRollbackException이 A까지 전달되지 않은것일까요?
0
A(REQUIRED) --> B(REQUIRES_NEW) --> C(REQUIRES_NEW)
디버깅 결과
이상황에서는
C에서 발생한 Runtime Exception이 발생 , C 트랜잭션의 롤백마킹
2. B에서 예외를 캐치한 후, 마킹되어있는 롤백을 확인했더니
status.isGlobalRollbackOnly();가 false
그래서 정상 커밋됨.
3. 그래서 UnexpectedRollbackException이 발생하지않아 A는 정상커밋
0
안녕하세요. 조성락님
우선 질문하신 내용은 다음과 같았습니다.
A(REQUIRED)
B(REQUIRES_NEW) -> C(REQUIRED)
결과적으로 A와, B 2개의 물리 트랜잭션이 존재하는 것이지요. C의 경우에는 B에 포함된 논리적인 트랜잭션입니다.
이 경우 C에서 롤백 마킹을 하게 됩니다. 그러면 B에서 커밋을 하는 순간에 UnexpectedRollbackException이 발생하게 됩니다.
그런데 이 예외가 AOP에서 발생하기 때문에 [A] -> [B의 AOP] -> [B]와 같이 됩니다.
결과적으로 [B의 AOP]에서 UnexpectedRollbackException을 던집니다.
A가 이 예외를 명확하게 잡아서 예외를 제거하면 A를 정상 커밋할 수 있습니다.
A가 이 예외를 잡지 않고 그냥 둔다면 A도 예외가 발생했기 때문에 트랜잭션이 롤백됩니다.
감사합니다.
RepositoryTest의 패키지 위치가 domain인 이유
0
30
2
REQUIRES_NEW 해결 방법에 대해서 질문있습니다!!
0
30
1
update()에 사용하는 setter 질문드립니다.
0
47
1
SQL 중심적 개발의 문제점에 대한 질문
0
72
1
혹시 Containing 을 안쓰신 이유가 있을까요?
0
83
2
[공유] 스프링부트 4.x 버전 mybatis 연동
0
174
1
@repository 어노테이션
0
89
3
ItemService
0
58
1
논리 커밋, 물리 커밋 질문드립니다.
0
54
1
내부 트랜잭션 커밋은 필수인가요?
0
57
1
프록시 커넥션 객체를 반환할 때 생성하는건가요?
0
54
1
Transaction readOnly 성능 개선 (김영한님의 대한 감사인사)
2
178
2
JPQL 대신 네이티브 쿼리를 사용해야 하는 경우
0
77
1
@EventListener(ApplicationReadyEvent.class) 관련
0
88
1
트랜잭션 동기화 매니저와 데이터 소스
0
76
1
DB 관련 강의 개설 계획은 없으신건가요?
0
133
2
물리 트랜잭션 과 논리트랜잭션 용어를 맞게 이해한걸까요
0
94
1
스프링 3 버전 이상 rollbackFor 변경된듯요
1
112
1
트랜잭션 전파 질문.
0
87
1
프로젝트 오픈 에러
0
126
1
외부 트랜잭션에서 isNewTransaction이 false로 나오는거에 대해 질문드립니다
0
83
2
같은 스레드를 사용하면 트랜잭션 동기화 매니저는 같은 커넥션을 반환
0
72
1
h2 인메모리 테스트중 예약어 충돌날 경우 대처방법
0
102
1
커스텀aop와 트랜잭션을 같이 사용할때 우선순위에 관한 질문
0
98
2





