ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • @Transactional 왜 안됨..?
    카테고리 없음 2022. 8. 6. 15:04

    우아한 테크코스 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을 걸었기 때문에, 일단 성능 개선을 우선순위를 미루기러 했습니다. 추후에, 위의 성능 이슈를 해결하고 꼭 다시 공유하도록 하겠습니다~!

    끗.

     

    Written By Chris(공병주)

Designed by Tistory.