우아한 테크코스 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이 열리지 않습니다.
@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는 되지 않아서 데이터가 계속 쌓이는 상황이었습니다.
아니 트랜잭션이 안열리는데 이게 왜 되는거지..?
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을 걸었기 때문에, 일단 성능 개선을 우선순위를 미루기러 했습니다. 추후에, 위의 성능 이슈를 해결하고 꼭 다시 공유하도록 하겠습니다~!
끗.