트러블 슈팅 https://velog.io/@yoon17710/TIL-QueryDSL
매니저 등록 요청 중 실패하더라도 등록 요청 로그를 DB에 저장하여 남겨야 한다.
매니저 등록 서비스 -> 서비스 내부에서 요청 로그 DB 저장 -> 매니저 DB 저장 완료
이 과정은 하나의 트랜잭션으로 묶여 있으며, 만약 오류 발생 시 매니저, 로그 둘다 DB에서 저장이 롤백된다.
실패하더라도 등록 요청 로그만은 DB에 저장되도록 하려면, 트랜잭션의 전파(Propagation) 속성을 적절히 활용해야 한다.
그중에서 REQUIRES_NEW 옵션은 기존 트랜잭션에 참여하지 않고 새로운 트랜잭션을 생성하는 설정이다.
REQUIRES_NEW 전파 속성이 적용된 메서드에서 저장하도록 한다. @Transactional
public ManagerSaveResponse saveManager(CustomUserDetails userDetails, long todoId, ManagerSaveRequest managerSaveRequest) {
...
// 요청 로그 저장
logService.saveManagerRegisterRequest(user.getId(), managerSaveRequest.getManagerUserId());
...
// 매니저 등록 저장
Manager newManagerUser = new Manager(managerUser, todo);
Manager savedManagerUser = managerRepository.save(newManagerUser);
}
매니저 등록 서비스에는 @Transactional을 걸어주고
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveManagerRegisterRequest(Long userId, Long managerId) {
Log log = new Log(userId,
"Manager register - Request ID: "+userId+", Manager ID: "+managerId);
logRepository.save(log);
}
로그 저장 서비스에는 @Transactional(propagation = Propagation.REQUIRES_NEW) 전파 속성을 설정한다.
처음에는 로그 저장을 별도의 서비스로 분리하지 않고 매니저 서비스 내부에서 logRepository.save()를 직접 호출하여 사용하였다.
매니저 등록 서비스에 @Transactional(propagation = Propagation.REQUIRES_NEW)를 적용하고 logRepository.save() 는 try-catch 내부 블록에서 실행하도록 했지만, 예상과 다르게 로그가 저장되지 않았다.
logRepository.save()는 JPA의 save 메서드로, 내부적으로 트랜잭션이 적용되어 있으므로 별도의 트랜잭션이 생성될 거라 생각했지만, 실제로는 REQUIRES_NEW가 적용되지 않았다.
찾아보니 트랜잭션의 전파 속성(propagation)은 외부에서 호출될 때만 적용된다는 것을 알게 되었다.
즉, logRepository.save() 역시 내부적으로 트랜잭션이 적용되어 있지만
REQUIRES_NEW가 아닌 기본 옵션 REQUIRED의 트랜잭션으로 실행되기 때문에,
Transactional(propagation = REQUIRES_NEW)가 선언된 메서드처럼 동작하지 않는다.
@Transactional(propagation = REQUIRES_NEW)를 적용한 뒤, 매니저 서비스에서 해당 로그 서비스의 메서드를 호출하는 방식으로 변경하였다.➡️ 따라서 @SpringBootTest를 적용한 실제 DB와 연결되는 환경에서 테스트해야 한다.
@SpringBootTest
@Transactional
class ManagerServiceTest {
@Autowired
ManagerService managerService;
@Autowired
LogRepository logRepository;
@Test
@DisplayName("매니저 등록 로깅 트랜잭션 테스트")
void saveManager() {
// given : 매니저 등록을 할 때
User user = User.builder()
.email("email@email.com")
.id(1L)
.userRole(UserRole.USER)
.nickname("nick")
.password("password")
.build();
ManagerSaveRequest request = new ManagerSaveRequest(2L);
CustomUserDetails userDetails = new CustomUserDetails(user);
Long todoId = 1L;
// when : 매니저 등록을 할 때
try {
managerService.saveManager(userDetails, todoId, request);
}
catch (InvalidRequestException e) {}
// then : 매니저 등록 실패해도 로깅에 성공
assertEquals(logRepository.count(), 1L);
}
}
이거 중간중간에 트랜잭션이나
재시도 처리시킬게 많은데
이걸 어떻게 나눠야하지?
그리고 유저 서비스가 아니라 이미지 서비스를 호출하는 형식으로 하다보니
뭔가 결합도가 생긴것 같다..
일단 S3 파일 업로드 로직을 정리해야겠다

로컬 임시 파일을 비동기 작업으로 돌리고, 실패하면 최대 3회 반복해서 재시도 하도록 설정한다
만약 파일 삭제에 실패해서 서버 내부에 불필요한 파일이 쌓이면 메모리 낭비니까
프로필 수정 기능은
내부의 프로필 삭제 메소드 호출 + 프로필 업로드 메소드 호출로 했는데
문제는 둘다 트랜잭션이 걸려있고 수정에도 트랜잭션이 걸려있다
삭제 메소드(S3 삭제 → user-img 연관관계 삭제)
업로드 메소드(임시 파일 저장 → S3 업로드 → 임시 파일 삭제 → url return → image 엔티티 생성 → user에 연관관계 설정)
일단 삭제 메소드에는 REQUIRES_NEW 로 걸어뒀다
만약에 롤백이 되어서 delete한 연관관계가 돌아온다고해도 S3에 삭제된 파일의 링크를 사용하면 오류가 발생할 수 있으니
AWS S3 트러블 슈팅 (https://velog.io/@yoon17710/TIL-Spring-MultipartAWS-S3-%EC%A0%81%EC%9A%A9
트러블 슈팅 (https://velog.io/@yoon17710/TIL-massive-data-handling-jpa)