JPA에서 Detach상태를 Persist 상태로 만들기 - 프로젝트 Issue 해결

김건우·2023년 1월 27일
0

Spring Data JPA

목록 보기
8/11
post-thumbnail

프로젝트에서 JPA를 사용하면서 진행중인데 , 어떠한 특정 값이 Null로 들어오는 NullPointerException이 발생하는 것을 확인했다. 결국 디버깅을 통해서 특정 값이 null로 들어오는 것을 확인했는데 , 이제 코드로 설명하겠다.

Controller

  • 우선 다음의 컨트롤러는 회원가입한 회원의 이메일과 회원가입 성공 토큰을 이용하여 유효성 검사하는 controller이다.
/**유효한 이메일인지 check**/
    @GetMapping("/check-email-token")
    public String checkEmailToken(String token,String email,Model model) {
        Account account = accountRepository.findByEmail(email);
        String view = "account/checked-email";
        //1.email이 존재하는지
        //2.emailCheckToken이 일치하는지
        if (account == null) {
            model.addAttribute("error", "wrong.email");
            return view;
        }
        if (!account.getEmailCheckToken.equals(token)) {
            model.addAttribute("error", "wrong.token");
            return view;
        }
        account.setEmailVerified(true);
        account.setJoinedAt(LocalDateTime.now());
        model.addAttribute("numberOfUser", accountRepository.count());
        model.addAttribute("nickname", account.getNickname());
        return view;
    }

하지만 문제는 2번째 검사를 해주는 조건문 안의 account.getEmailCheckToken의 값이 계속해서 null로 들어오는 것이었다. 그렇다. 잘 생각해보면 문제는 애초에 회원가입에서의 발급해주는 이메일 인증 토큰 값이 데이터베이스에 저장이 안된것이다. 그러면 무엇이 문제였을까?
그것을 확인하기위해 회원가입에서의 service 단을 살펴보겠다.🔽
회원가입 service

  • save()와 sendSignUpEmail()메서드를 사용하여 회원가입 후 인증 이메일 발송하는 메서드
 public void processNewAccount(@Valid SignUpForm signUpForm) {
        //회원 저장
        Account newAccount = save(signUpForm);
        //저장 완료 시 이메일 체크에 필요한 토큰 발급🔽
        newAccount.generateEmailCheckToken(); /**newAccount라는 객체는 detached 상태이기 때문에 @Transactional을 붙여줘서 persist상태를 유지시켜준다.  **/
        sendSignUpEmail(newAccount);
    }
  • 디비 save() 메서드
  /**회원 가입 save 메서드**/
    private Account save(@Valid SignUpForm signUpForm) {
        String encode = passwordEncoder.encode(signUpForm.getPassword());
        Account account = signUpForm.of(encode);
        return accountRepository.save(account);
    }
  • Account 객체에 emailCheckedToken 값을 넣어주는 메서드
  /**계정 생성시 UUID를 사용하여 랜덤한 토큰 값 발급**/
    public void generateEmailCheckToken() {
        this.emailCheckToken = UUID.randomUUID().toString();
        this.emailCheckTokenGeneratedAt = LocalDateTime.now();
    }

이미 Detached 상태가 되어버린 객체

문제는 바로 두번째 메서드에서 이미 📢영속성 컨텍스트에 있는 것을 commit 해줘서 persist상태가 아닌 detached 상태가 되어버린것이다.
즉, 디비에 저장 후 newAccount 객체는 detached 객체라서 generateEmailCheckToken() 메서드를 사용해서 emailCheckToken값을 넣어주려고 할 때 데이터베이스와 Sync가 안 맞게된다.

해결방법-> 다시 Transactional에 넣어주자!

쉽게 다시말하자면 하나의 객체를 이미 commit하여서 그 객체는 detached 객체가 되었다.그래서 디비와의 Sync가 안 맞게 된것이다. 따라서 트랜잭션의 범위를 넓혀주어야한다. 즉 회원 가입시 emailCheckToken값도 디비에 저장하게 하려면 processNewAccount메서드에 @Transactional을 붙여주어야한다.

  • Transactional 범위를 넓혀주어서 detach이었던 객체를 persist객체 상태로 유지시킨다. persist 객체는 transactional이 종료될 때 디비와 Sync가 됨으로 값이 저장된다.
 @Transactional
    public void processNewAccount(@Valid SignUpForm signUpForm) {
        //회원 저장
        Account newAccount = save(signUpForm);
        //저장 완료 시 이메일 체크에 필요한 토큰 발급🔽
        newAccount.generateEmailCheckToken(); /**newAccount라는 객체는 detached 상태이기 때문에 @Transactional을 붙여줘서 persist상태를 유지시켜준다.  **/
        sendSignUpEmail(newAccount);
    }
profile
Live the moment for the moment.

0개의 댓글