주요 특징:
- 새로운 트랜잭션 생성: 메소드가 호출될 때 기존 트랜잭션이 있더라도 그것을 무시하고 항상 새로운 트랜잭션을 생성한다.
- 독립적인 트랜잭션: 이 새로운 트랜잭션은 기존 트랜잭션과 완전히 독립적으로, 새로 시작된 트랜잭션이 커밋되거나 롤백되더라도, 기존 트랜잭션에는 영향을 미치지 않는다.
- 원래 트랜잭션 중단: 새로운 트랜잭션이 끝날 때까지 기존 트랜잭션은 중단된다. 새로운 트랜잭션이 완료된 후에 기존 트랜잭션이 다시 이어진다.
- 부분 커밋: 메소드 실행 중에 새로운 트랜잭션이 성공적으로 커밋되면, 그 결과는 전체 트랜잭션의 성공 여부와 상관없이 데이터베이스에 영구적으로 반영된다. 즉, 새 트랜잭션이 커밋되면 그 변경 사항은 롤백되지 않는다.
□ 문제사항
@Transactional
public ManagerSaveResponse saveManager(AuthUser authUser, long todoId, ManagerSaveRequest managerSaveRequest) {
// 일정을 만든 유저
User user = User.fromAuthUser(authUser);
try {
createLog(todoId, managerSaveRequest.getManagerUserId(), user.getId());
} catch (Exception e) {
logger.error("Failed to save log", e);
// 예외를 던지지 않음으로써 롤백 방지
}
Todo todo = todoRepository.findById(todoId)
.orElseThrow(() -> new InvalidRequestException("Todo not found"));
if (todo.getUser() == null || !ObjectUtils.nullSafeEquals(user.getId(), todo.getUser().getId())) {
throw new InvalidRequestException("담당자를 등록하려고 하는 유저가 유효하지 않거나, 일정을 만든 유저가 아닙니다.");
}
User managerUser = userRepository.findById(managerSaveRequest.getManagerUserId())
.orElseThrow(() -> new InvalidRequestException("등록하려고 하는 담당자 유저가 존재하지 않습니다."));
if (ObjectUtils.nullSafeEquals(user.getId(), managerUser.getId())) {
throw new InvalidRequestException("일정 작성자는 본인을 담당자로 등록할 수 없습니다.");
}
Manager newManagerUser = new Manager(managerUser, todo);
Manager savedManagerUser = managerRepository.save(newManagerUser);
return new ManagerSaveResponse(
savedManagerUser.getId(),
new UserResponse(managerUser.getId(), managerUser.getEmail())
);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void createLog(Long todoId, Long managerId, Long requestUserId) {
Log saveLog = new Log(todoId, managerId, requestUserId);
logRepository.save(saveLog);
}
- saveManager실행 시 createLog를 실행하여 Log에 대한 기록을 남기려고함
- saveManager이 실패하게되면 롤백이되면서 Log가 DB에 저장되지못함
- 트랜잭션 전파: createLog 메소드는 REQUIRES_NEW 전파 모드를 사용하므로 saveManager와는 별도의 트랜잭션에서 실행된다. 즉, saveManager 트랜잭션과는 독립적으로 로그를 저장하는 트랜잭션이 시작되고 커밋될 수 있다.
- 하지만 이 전파 모드는 createLog 트랜잭션이 완전히 독립적으로 동작할 때만 의미가 있다.
- 동일한 클래스 내에서 saveManager가 createLog를 호출하면, Spring은 이를 프록시 기반으로 처리하기 때문에 실제로는 별도의 트랜잭션이 생성되지 않고 하나의 트랜잭션으로 동작할 수 있다.
- 내부 호출 문제: Spring의 AOP 트랜잭션 관리 방식은 동일한 클래스 내에서의 메소드 호출에 대해 프록시를 생성하지 않는다. 즉, saveManager가 createLog를 호출할 때 트랜잭션 전파 규칙이 적용되지 않아, REQUIRES_NEW가 제대로 작동하지 않고, 상위 트랜잭션이 롤백되면 하위 트랜잭션도 롤백된다.
- 트랜잭션 롤백: saveManager에서 예외가 발생하면 기본적으로 @Transactional은 그 예외를 롤백 처리, 이때 createLog가 상위 트랜잭션의 영향을 받아서 롤백되므로, 로그가 데이터베이스에 저장되지 않게 된다.
-> 결국 해당 문제도 지난번에 겪었던 self-invocation문제와 같은상황이다.
https://djhelloworld.tistory.com/164
□ 해결방법
LogService를 분리하여 작업을 처리함
@Transactional
public ManagerSaveResponse saveManager(AuthUser authUser, long todoId, ManagerSaveRequest managerSaveRequest) {
// 일정을 만든 유저
User user = User.fromAuthUser(authUser);
Log saveLog = logRepository.findById(logService.createLog(todoId, managerSaveRequest.getManagerUserId(), user.getId()))
.orElseThrow(()-> new RuntimeException("해당 로그를 찾지 못했습니다."));
try {
Todo todo = todoRepository.findById(todoId)
.orElseThrow(() -> new InvalidRequestException("Todo not found"));
if (todo.getUser() == null || !ObjectUtils.nullSafeEquals(user.getId(), todo.getUser().getId())) {
throw new InvalidRequestException("담당자를 등록하려고 하는 유저가 유효하지 않거나, 일정을 만든 유저가 아닙니다.");
}
User managerUser = userRepository.findById(managerSaveRequest.getManagerUserId())
.orElseThrow(() -> new InvalidRequestException("등록하려고 하는 담당자 유저가 존재하지 않습니다."));
if (ObjectUtils.nullSafeEquals(user.getId(), managerUser.getId())) {
throw new InvalidRequestException("일정 작성자는 본인을 담당자로 등록할 수 없습니다.");
}
Manager newManagerUser = new Manager(managerUser, todo);
Manager savedManagerUser = managerRepository.save(newManagerUser);
saveLog.setSuccess(true);
return new ManagerSaveResponse(
savedManagerUser.getId(),
new UserResponse(managerUser.getId(), managerUser.getEmail())
);
}catch (Exception e){
throw e;
}
}
- try-catch를 사용해서 Log에 API성공 확인을 위한 필드를 추가 후 처리
'Java & Spring > 트러블슈팅' 카테고리의 다른 글
MultipleBagFetchException(두개의 Fetch Join) (0) | 2024.10.08 |
---|---|
Self-invocation(자기 호출) (0) | 2024.09.05 |