본문 바로가기
Project/소경관

[소경관] : JPA와 builder 패턴을 사용한 회원 가입 및 Id, Email 중복 체크 로직 코딩하기

by 오주현 2022. 4. 14.
반응형

📌 검증 기능은 아직 구현하지 못 했기 때문에 회원 가입과 ID, Email 중복 체크를 중점으로 보면 된다.


@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "USER_INFO")
@ToString
public class UserEntity {

    @Id @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long userNo;

    @Column(name = "USER_NAME", length = 20)
    private String userName;

    @Column(name = "USER_PN", length = 40)
    private String userPn;

    @NotNull
    @Column(name = "USER_EMAIL", length = 200)
    private String userEmail;

    @NotNull
    @Column(name = "USER_ID", length = 200)
    private String userId;

    @Column(name = "USER_PW", length = 200)
    private String userPw;

    @Column(name = "USER_ADDR", length = 200)
    private String userAddr;

    @Builder
    public UserEntity(Long userNo,
                      String userName,
                      String userPn,
                      String userEmail,
                      String userId,
                      String userPw,
                      String userAddr) {
        this.userNo = userNo;
        this.userName = userName;
        this.userPn = userPn;
        this.userEmail = userEmail;
        this.userId = userId;
        this.userPw = userPw;
        this.userAddr = userAddr;
    }

}

UserEntity.java

Entity에는 builder를 적용했다.

 

@Data
public class UserSaveForm {

    private Long userNo;

    @NotBlank(message = "이름을 입력해주세요.")
    @NotNull
    private String userName;

    @NotBlank(message = "연락처를 입력해주세요.")
    private String userPn;

    @NotBlank(message = "이메일 주소를 입력해주세요.")
    @Email(message = "올바른 이메일 주소를 입력해주세요.")
    private String userEmail;

    @NotBlank(message = "아이디를 입력해주세요.")
    private String userId;

    @NotBlank(message = "비밀번호를 입력해주세요.")
    @Size(min = 8, max = 20, message = "비밀번호는 8자 이상 20자 이하로 입력해주세요.")
    private String userPw;

    @Column(name = "USER_ADDR", length = 200)
    private String userAddr;

}

UserSaveForm.java

 

@Data
public class UserDTO {

    private Long userNo;
    private String userName;
    private String userPn;
    private String userEmail;
    private String userId;
    private String userPw;
    private String userAddr;

    public UserDTO(Long userNo,
                   String userName,
                   String userPn,
                   String userEmail,
                   String userId,
                   String userPw,
                   String userAddr) {
        this.userNo = userNo;
        this.userName = userName;
        this.userPn = userPn;
        this.userEmail = userEmail;
        this.userId = userId;
        this.userPw = userPw;
        this.userAddr = userAddr;
    }
}

UserDTO.java

Entity, DTO, VO를 사용해서 데이터를 전달한다.

 

/**
     * 회원가입 페이지로 이동
     */
    @GetMapping("/user/regUser")
    public String regUserForm(Model model) {

        log.info(this.getClass().getName() + ".user/regUser 회원가입으로 이동 !!");

        model.addAttribute("userSaveForm", new UserSaveForm());

        return "user/regUser";
    }

회원가입 페이지로 이동할 때 Model 객체에 담아 데이터를 담아 올 VO를 보낸다.

 

<form th:action="@{/user/regUser/insert}" th:object="${userSaveForm}" id="contactForm" data-sb-form-api-token="API_TOKEN" th:method="post">
  <div th:hidden="*{userNo}"></div>
  <div class="form-floating">
      <input class="form-control" id="userName" th:field="*{userName}" type="text" placeholder="이름을 입력하세요" data-sb-validations="required" />
      <label for="userName">이름</label>
      <div class="invalid-feedback" data-sb-feedback="name:required">A name is required.</div>
  </div>

th:object로 값을 받을 vo를 설정하고 th:action으로 다시 Controller로 submit해준다.

 

/**
     * 회원가입 로직 처리
     */
    @PostMapping("/user/regUser/insert")
    public String InsertRegUser(@Valid UserSaveForm form, BindingResult result) throws Exception{

        log.info(this.getClass().getName() + "회원가입 로직 처리 시작");

        /**
         * 1. @Valid 검증 로직 실행
         */
        if (result.hasErrors()) {
            log.info(" 회원가입 로직 처리 중 Errors 처리 result ={}", result);
            return "user/regUser";
        }

        /**
         * 2. DTO 생성자에 VO 값 세팅
         */
        UserDTO userDTO = new UserDTO(
                form.getUserNo(),
                form.getUserName(),
                form.getUserPn(),
                form.getUserEmail(),
                form.getUserId(),
                form.getUserPw(),
                form.getUserAddr()
        );

        log.info("UserDTO ={}", userDTO);

        /**
         * 3. 회원 가입 정보 DTO를 Controller -> Service 전달
         */
        userService.InsertUser(userDTO);

        return "/user/logIn";
    }

vo로 받아온 값을 DTO에 넣어준다.

 

찾아보니 가능하면 Setter를 안 쓰는 게 좋다고 해서 DTO에 생성자를 만들고 값을 파라미터로 받아 넘겨주는 식으로 데이터를 옮겨줬다.

 

vo → dto로 데이터를 담고 Service로 보내준다.

 

@Slf4j
@RequiredArgsConstructor
@Service
public class UserService implements IUserService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public void InsertUser(UserDTO userDTO) throws Exception {

        log.info(this.getClass().getName() + "로그인 처리 Controller -> Service");

        /**
         *  1. builder pattern 활용하여 Entity에 값을 세팅
         */
        UserEntity userEntity = UserEntity.builder()
                .userNo(userDTO.getUserNo())
                .userName(userDTO.getUserName())
                .userPn(userDTO.getUserPn())
                .userEmail(EncryptUtil.encHashSHA256(userDTO.getUserEmail()))
                .userId(userDTO.getUserId())
                .userPw(EncryptUtil.encHashSHA256(userDTO.getUserPw()))
                .userAddr(userDTO.getUserAddr())
                .build();

        log.info("userEntity에 담긴 값 확인 ={}", userEntity);

        /**
         *  2. ID 및 Email 중복 체크
         */
        validateDuplicateUser(userEntity);

        /**
         * 5. 중복 체크 완료 후 회원 가입
         */
        userRepository.save(userEntity);
    }

    private void validateDuplicateUser(UserEntity userEntity) {

        log.info(this.getClass().getName() + "중복 체크 로직 실행");
        log.info("중복 체크를 위해 받아온 아이디 값 = {}", userEntity.getUserId());
        log.info("중복 체크를 위해 받아온 이메일 값 = {}", userEntity.getUserEmail());

        /**
         * 3. 회원가입 중 ID 중복 체크
         */
        List<UserEntity> findUserId = userRepository.findId(userEntity.getUserId());

        log.info("로직 후 아이디 ={}", findUserId);

        if (!findUserId.isEmpty()) {
            throw new IllegalArgumentException("이미 존재하는 아이디입니다.");
        }

        /**
         * 4. 회원가입 중 Email 중복 체크
         */
        List<UserEntity> findUserEmail = userRepository.findByEmail(userEntity.getUserEmail());

        log.info("로직 후 이메일 ={}", findUserEmail);

        if (!findUserEmail.isEmpty()) {
            throw new IllegalArgumentException("이미 존재하는 이메일입니다.");
        }

        log.info(this.getClass().getName() + "중복 체크 로직 종료");
    }

}

Service는 interfaceService를 만들고 구현하는 식으로 만들었다.

 

주석으로 순서를 체크해 뒀다 참고하면 된다.

  1. 최종적으로 Entity에 값을 담을건데 Entity는 Setter를 사용하지 않는 게 좋다 하여 builder 패턴을 사용했고 이때 dto → entity로 데이터를 넘기면서 암호화 로직을 적용했다.
  2. ID와 Email을 체크하기 위해 따로 메소드를 만들었고 체크할 값이 담긴 entity를 넘겨준다.
  3. Repository에 만들어둔 체크 로직 실행을 위해 값을 보내주고 리턴 받은 값에 따라 IllegalArgumentException 을 보낸다.
  4. 3번과 마찬가지로 적용된다.
  5. 중복 체크를 완료했다면 다시 Repository에 만들어둔 저장 로직을 실행시키기 위해 entity 객체를 보내준다.

 

@Slf4j
@Transactional
@Repository //스프링 빈으로 등록한다.
public class UserRepository {

    @PersistenceContext
    private EntityManager em;

    /**
     * 회원 데이터 저장
     * @param userEntity
     */
    public void save(UserEntity userEntity) {
        em.persist(userEntity);
    }

    /**
     * UserService에서 회원 가입 시 ID 중복 체크
     * @param userId
     * @return
     */
    public List<UserEntity> findId(String userId) {
        log.info("레포지토리에서 이메일 조회 시작");
        log.info("입력한 이메일 값 ={}", userId);
        log.info("체크 된 이메일 값 = {}", em.createQuery("select m from UserEntity m where m.userId = :userId", UserEntity.class));
        log.info("레포지토리에서 이메일 조회 끝");
        return em.createQuery("select m from UserEntity m where m.userId = :userId", UserEntity.class)
                .setParameter("userId", userId)
                .getResultList();
    }

    /**
     * UserService에서 회원 가입 시 Email 중복 체크
     * @param userEmail
     * @return
     */
    public List<UserEntity> findByEmail(String userEmail) {
        log.info("레포지토리에서 이메일 조회 시작");
        log.info("입력한 이메일 값 ={}", userEmail);
        log.info("체크 된 이메일 값 = {}", em.createQuery("select m from UserEntity m where m.userEmail = :userEmail", UserEntity.class));
        log.info("레포지토리에서 이메일 조회 끝");
        return em.createQuery("select m from UserEntity m where m.userEmail = :userEmail", UserEntity.class)
                .setParameter("userEmail", userEmail)
                .getResultList();
    }

}

EntityManager를 통해 로직을 만들어 주었다. JpaRepository<>를 사용하면 좀 더 편하게 할 수 있지만 JPA를 사용해 보지 못 했기 때문에 자세히 알고 가려면 순수 JPA를 사용해 보는 게 더 좋다고 해서 이렇게 해 봤다.

 

맨 위에는 일단 저장 로직을 만들었다.

 

회원 ID 체크와 Email 체크는 JPQL을 사용했다. find()를 통해 조회하려고 했는데 pk값이 아니면 조회가 안 되는 것 같다.

이렇게 되면 회원 가입이 완료된다. DB에 값도 정상 저장되고 이메일과 아이디가 중복되지 않는다.


반응형

댓글