카테고리 없음

@Transactional 트러블슈팅

kjw81024 2026. 4. 6. 19:14

일단 Transactional option 이 뭔지 잘 모르겠어서 찾아봤다

@Transactional(
    propagation = Propagation.REQUIRED, # 전파 옵션 (이미 transaction 이 있을때 어떻게 할지)
    isolation = Isolation.DEFAULT, # 격리 수준: 기본은 DB 설정 따르기 
    readOnly = false, # 조회전용 (dirty checking 안할게염)
    rollbackFor = Exception.class, # 언제 rollback 을 할건지
    timeout = 30 # transaction 을 얼마간 잡고있을건지 -> 30초 넘으면 롤백
)

 

 

이번에 쓰는건 propagation 이라 해당부분만 추가

REQUIRED 기존 트랜잭션 있으면 참여, 없으면 생성 -> 기본
propagation 옵션 설정 안 하면 얘임 
REQUIRES_NEW 기존 트랜잭션 있으면 잠깐 중단하고 새 트랜잭션 생성
-> 원래 비즈니스 로직에서 exception 던져져도 log 는 그대로 기록
SUPPORTS 트랜잭션이 실행중이면 참여함 
-> exception 뜨면 rollback 되고, exception 안 뜨면 저장 되고 
transaction 이 없다면 그냥 transaction 없이 실행
NOT_SUPPORTED 트랜잭션 실행중이면 중단
MANDATORY 반드시 트랜잭션 필요 
없다면 exception -> 무조건 트랜잭션 안에서만 실행되어야함 
NEVER mandatory 와 반대 : 트랜잭션 있으면 예외 
NESTED 부모 트랜잭션 내부 savePoint 생성 -> 일부 롤백 가능 

 

일단 commit 이 되는것과 관계없이 모든 요청에 log 를 남겨야하는거니까 Requires_new 를 사용하기로 결정!!

 

더보기

EventListener 랑 같이 쓰는 경우가 있다고 함

applicationEventPublisher.publishEvent(event);

# 이 때 after commit 말고 after completion 을 쓰면 성공, 실패 상관 없이 실행되므로 
# 과제에 event listener 를 썼다면 얘를 썼을듯

# 이후에 commit 이 완료되었다 하면 성공 결과 저장용으로 after commit,
# exception 이 발생했다고 하면 after rollback 으로 저장했을 것 같다 

@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handle(Event e){
	ManagerLogService.saveInitial( );
}

문제

 

manager save log 를 남길 때 처음에 Manager Service 에서 아래처럼 둘 다 작성하려고 했음

@Transactional
public ManagerSaveResponse saveManager
	(AuthUser authUser, long todoId, ManagerSaveRequest managerSaveRequest) {
    # 요청이 들어왔을 때 바로 log 를 생성
    saveInitialLog(authUser.getId(), todoId, managerSaveRequest.getManagerUserId());

    # 생략
}

# level 11 : 매니저 등록 요청 시 로그 기록
# transaction option 을 활용해서 save manager 와 logging 이 개별로 이루어질 수 있도록 하기
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveInitialLog(Long loginUserId, Long todoId, Long managerId){
    ManagerLog log = new ManagerLog(loginUserId, todoId, managerId);
    managerLogRepository.save(log);
}

 

근데 실패하는 경우만 로그가 안 남는다 . . . 이유를 모르겠어서 더 고민해보니까

같은 클래스의 내부 호출을 하면 @Transactional 이 무시됨 (들었던 거 같은데 까먹었다. . . )

 

저장이 안 되는 흐름은 아래와 같다 

 

Spring transaction 은 proxy 기반인데 동일 클래스면 proxy 를 안 거침

-> 그냥 일반 메서드가 호출

-> 한 transaction 안에 묶임

-> REQUIRES_NEW 안 됨

-> exception 나면 저장 아무것도 안 됨

 

뭐 이런 .... 로직인듯 하다 

 

일단 class 를 분리해줌 

@Service
@RequiredArgsConstructor
public class ManagerLogService {
    private final ManagerLogRepository managerLogRepository;

    # level 11 : 매니저 등록 요청 시 로그 기록
    # transaction option 을 활용해서 save manager 와 logging 이 개별로 이루어질 수 있도록 하기
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public Long saveInitialLog(Long loginUserId, Long todoId, Long managerId){
        ManagerLog log = new ManagerLog(loginUserId, todoId, managerId);
        ManagerLog savedLog = managerLogRepository.save(log);
        return savedLog.getId();
    }

    # 해당 요청이 성공했는지 실패했는지,,, 결과값도 저장
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void saveResultLog(Long logId, boolean status, String message){
        ManagerLog log = managerLogRepository.findById(logId).orElseThrow();
        log.addResult(status, message);
    }
}
@Transactional
public ManagerSaveResponse saveManager(
	AuthUser authUser, long todoId, ManagerSaveRequest managerSaveRequest) {
    # 요청이 들어왔을 때 바로 log 를 생성
    Long logId = managerLogService.saveInitialLog(
    	authUser.getId(), todoId, managerSaveRequest.getManagerUserId());

    # try catch 로 감싸서 error 발생하면 잡아서 log 에 추가해주기
    try{
        # 비즈니스 로직 생략

        # 성공은 return 전에 기록
        managerLogService.saveResultLog(logId, true, "매니저 등록에 성공했습니다.");
        return new ManagerSaveResponse(
                savedManagerUser.getId(),
                new UserResponse(managerUser.getId(), managerUser.getEmail())
        );
    }catch (Exception e){
        # 실패는 잡아서 개별 메시지도 넣어주기
        managerLogService.saveResultLog(logId, false, e.getMessage());
        throw e;
    }
}

값이 정상적으로 저장 

야호