@Transactional이 왜 안되지..?

공병주(Chris)·2022년 7월 20일
1
post-thumbnail

우아한 테크코스 Level3 팀 프로젝트 서비스에서 smtp를 통한 email 인증 기능이 있었습니다.

public void sendCodeToValidUser(EmailRequest emailRequest) {
    String serialNumber = encryptor.encrypt(emailRequest.getEmail());
    authService.validateSignUpMember(serialNumber);

    String authCode = saveAuthCode(serialNumber);
    sendEmail(emailRequest, authCode);
}

@Transactional
protected String saveAuthCode(String serialNumber) {
    authCodeRepository.deleteAllBySerialNumber(serialNumber);
    String authCode = authCodeGenerator.generate();
    authCodeRepository.save(new AuthCode(authCode, serialNumber));
    return authCode;
}

private void sendEmail(EmailRequest emailRequest, String authCode) {
    emailSender.send(emailRequest.getEmail(), authCode);
} 

emailSender.send가 호출되면 사용자에게 이메일을 발송을 하는데, 이메일 발송하는데 4-5초 가량의 시간이 걸려서 sendCodeToValidUser 메서드에 트랜잭션을 걸지 않았습니다. 이메일을 발송하는 오랜 시간 동안, 다른 요청에 따른 DB 접근이 불가능한 것을 피하기 위해서였습니다.

예상대로 동작하지 않음. 왜?

그 이유는 아래와 같습니다.

저희는 saveAuthCode가 호출되었을 때, 트랜잭션이 열릴 것이라고 예상했습니다.
하지만, 객체의 진입 메서드(위 코드에서는 sendCodeToValidUser)에 @Transactional이 걸리지 않으면,
내부에서 @Transactional이 걸린 메서드(saveAuthCode)가 호출되어도 Transaction이 열리지 않습니다.

authCodeRepository.save는 동작하고 authCodeRepository.delete는 작동하지 않는 이유..?

@Transactional
protected String saveAuthCode(String serialNumber) {
    authCodeRepository.deleteAllBySerialNumber(serialNumber); // 1
    String authCode = authCodeGenerator.generate();
    authCodeRepository.save(new AuthCode(authCode, serialNumber)); // 2
    return authCode;
} 

내부에서 호출된 saveAuthCode 메서드에서
1으로 표시한 authCodeRepository.deleteAllBySerialNumber는 수행되지 않고,
2로 표시한 authCodeRepository.save는 수행이 잘 되었습니다.

따라서, 해당 메서드가 호출되면 save만 되고 delete는 되지 않아서 데이터가 계속 쌓이는 상황이었습니다.

아니 트랜잭션이 안열리는데 이게 왜 되는거지..?

DataJpaRepository의 save에는 @Transactional이 걸려있다.

public interface AuthCodeRepository extends JpaRepository<AuthCode, Long> {
    Optional<AuthCode> findBySerialNumber(String serialNumber);

    void deleteAllBySerialNumber(String serialNumber);
}

저희 팀에서 작성한 AuthCodeRepository입니다. JpaRepository 인터페이스를 상속하고 있습니다.

JpaRepository 인터페이스를 구현한 구현체의 save 메서드에 @Transactional이 걸려있습니다.

Service 객체에서 Transaction이 열리지 않았어도, AuthCodeRepository는 save 메서드가 객체의 진입 메서드이기 때문에, save가 호출되는 순간 Transaction이 열리는 것입니다.

하지만, 저희가 정의한 deleteAllBySerialNumber에는 @Transactional을 걸어주지 않았기 때문에,
Service에서도 Transaction이 열리지 않았고, deleteAllBySerialNumber이 호출될 때도 Transaction이 열리지 않기 때문에 삭제가 되지 않습니다.

그래서 어떻게 해결했는데?

팀 결론 : 일단 성능을 고려하지 말고.. 기능 구현이 급하니까, 추후에 성능 개선을 하자..!

@Transactional
public void sendCodeToValidUser(EmailRequest emailRequest) {
    String serialNumber = encryptor.encrypt(emailRequest.getEmail());
    authService.validateSignUpMember(serialNumber);

    String authCode = saveAuthCode(serialNumber);
    sendEmail(emailRequest, authCode);
}

private String saveAuthCode(String serialNumber) {
    authCodeRepository.deleteAllBySerialNumber(serialNumber);
    String authCode = authCodeGenerator.generate();
    authCodeRepository.save(new AuthCode(authCode, serialNumber));
    return authCode;
}

private void sendEmail(EmailRequest emailRequest, String authCode) {
    emailSender.send(emailRequest.getEmail(), authCode);
} 

성능 때문에 내부 메서드에서 @Transactional을 걸었기 때문에, 일단 성능 개선을 우선순위를 미루기러 했습니다. 추후에, 위의 성능 이슈를 해결하고 꼭 다시 공유하도록 하겠습니다~!

끗.

0개의 댓글