본문 바로가기

Development Log

Spring boot - 스프링부트 회원가입 아이디 중복체크 (프론트+백)

항상 개발하고 내용 정리는 Notion으로만 정리하다가 아이디 중복체크에 대한 로직을

쉽게 따라 할 수 있는 코드를 공유했으면 좋겠다는 생각에 Tistory를 시작하게 되었다.

 

왜냐하면,

아이디 중복체크에 대해서 정말 많은 검색을 했지만

쉽게 따라 할 수 있고 프론트+백이 같이 설명되어있는 게시글은 못 봤던 것 같다.

 

회원가입에서 아이디 중복체크를 할 때,

프론트에서 자바스크립트로 경고 문구를 띄어서 체크해준다고 해도

경고 문구를 무시하고 회원가입을 진행했다면 어떻게 될까?

당연히 백에선 처리를 안 해줬기 때문에 등록이 되는 오류가 발생할 것이다.

 

그렇기 때문에 프론트+백에서 전부 체크를 해줘야 한다.

 

아래 코드를 천천히 파악하고 구현한다면 회원가입 아이디 중복체크는 프론트+백에서 손쉽게 할 수 있을 것 같다.

 

 

1. MemberDto.java

package data.dto;
// 임포트 생략

@Setter
@Getter
@Alias("mdto")
public class MemberDto {

  private int num;
  @NotBlank(message = "필수 입력 항목입니다.")
  @Pattern(regexp = "^[a-zA-Z0-9]{4,20}$", message = "4자~20자의 아이디여야 합니다.")
  private String id;

  @NotBlank(message = "필수 입력 항목입니다.")
  @Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*[!@#$%^*+=-])(?=.*[0-9]).{8,25}$",
      message = "숫자+영문자+특수문자 조합으로 8자리 이상 사용해야 합니다.")
  private String pw;

  @NotBlank(message = "필수 입력 항목입니다.")
  @Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*[!@#$%^*+=-])(?=.*[0-9]).{8,25}$",
      message = "숫자+영문자+특수문자 조합으로 8자리 이상 사용해야 합니다.")
  private String pw_check;

// 나머지 생략
...
}

 

  • @NotBlank는 Bean Validation에서 제공하는 표준 Validation이며, 기본적으로 3가지 어노테이션이 있다.
  • @NotNull은 null만 허용하지 않고, @NotEmpty은 null과 "" 둘 다 허용하지 않고,
    @NotBlank는 null, "", " " 전부 허용하지 않는다고 한다.
  • @Pattern은 정규식을 만족하는지 검사하는 어노테이션이라고 쉽게 생각하면 된다.

 

2. JoinController.java

package data.controller;
// 임포트 생략

@Controller
public class JoinController {

  private final MemberService service;
  
  // 생성자 주입
  public JoinController(MemberService service) {
    this.service = service;
  }

  // 회원가입
  @PostMapping("/join/insert")
  public String insert(@Valid MemberDto dto, Errors errors, Model model)
      throws IOException {

    if (errors.hasErrors()) {
      // 회원가입 실패시 작성한 정보 유지!
      model.addAttribute("MemberDto", dto);

      Map<String, String> validatorResult = service.validateHandling(errors);
      for (String key : validatorResult.keySet()) {
        model.addAttribute(key, validatorResult.get(key));
      }
      // 유효성 검사가 맞지 않으면 폼으로 이동
      return "/users/join_form";
    }

    // 유효성 검사를 통과하면 등록 후 페이지 이동
    service.insertMember(dto);
    return "/users/join_success";
  }

  // 아이디 중복체크
  @ResponseBody
  @RequestMapping("/idCheck")
  public int idCheck(@RequestBody String id) {
	
    // id 입력칸에 작성한 id가 db에 있는지 확인
    int cnt = service.idCheck(id); // 있으면 1, 없으면 0
    return cnt; 
  }
}

 

  • @Valid는 객체에 명시된 조건을 검사하는 Annotation으로 검사해야 할 항목 앞에 붙여줘야 한다.

 

3. JoinService.java

package data.service;
// 임포트 생략

@RequiredArgsConstructor
@Service
public class JoinService {

  private final JoinMapper mapper;

  // 회원가입
  public void insertMember(MemberDto dto) {
    mapper.insertMember(dto);
  }

  public Map<String, String> validateHandling(Errors errors) {
    Map<String, String> validatorResult = new HashMap<>();
    for (FieldError error : errors.getFieldErrors()) {
      String validKeyName = String.format("valid_%s", error.getField());
      validatorResult.put(validKeyName, error.getDefaultMessage());
    }
    return validatorResult;
  }

  // 아이디 중복체크
  public int idCheck(String id) {
    return mapper.idCheck(id);
  }
  
  // 나머지 생략
  ...
}

 

 

4. JoinMapper (인터페이스로 생성)

package data.mapper;

import org.apache.ibatis.annotations.Mapper;
import data.dto.MemberDto;

@Mapper
public interface JoinMapper {

  // 회원가입
  public void insertMember(MemberDto dto);

  // 아이디 중복체크
  public int idCheck(String id);
  
  // 나머지 생략
  ...

}

 

 

5. JoinSql.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="data.mapper.JoinMapper">
	<insert id="insertMember" parameterType="mdto">
		insert into member (id,pw) 
		values (#{id},#{pw})
	</insert>
	
	<select id="idCheck" parameterType="String" resultType="int">
		select count(*) from member where id = #{id}
	</select>
	
</mapper>

 

 

6. Join.jsp

<tr>
	<th><span>*</span>아이디</th>
	<td>
		<input type="text" name="id" id="id" value="${MemberDto.id}"/>
		<p id="id_tag">${valid_id}</p>
	</td>
</tr>
<tr>
	<th><span>*</span>비밀번호</th>
	<td>
		<input type="password" name="pw" id="pw_form" value="${MemberDto.pw}"/>
		<p id="pw_tag">${valid_pw}</p>
	</td>
</tr>
<tr>
	<th><span>*</span>비밀번호 확인</th>
	<td>
		<input type="password" name="pw_check" id="pwcheck_form" value="${MemberDto.pw_check}"/>
		<p id="pwcheck_tag">${valid_pw_check}</p>
	</td>
</tr>

 

  • input의 value 값을 넣는 이유는 유효성 검사가 실패한 경우를 대비하여 기존 작성 내용을 불러온다.
  • ${valid_id}는 타임리프가 아닌 Jstl에서 사용할 때 에러 문구를 출력할 수 있다. (DTO의 Pattern Message 출력)

여기까지 Validation(Back) 유효성 검사와 ID 중복체크 로직까지만 완료되었다.

 

[왼쪽 : 값 없이 바로 회원가입 버튼 클릭시 / 오른쪽 : 정규식에 만족하지 않는 값 입력 후 회원가입 버튼 클릭시]


이제 아래 2가지만 더 진행하면 완벽하게 구현될 것이다.

  • ID 중복체크는 제이쿼리로 입력하는 값에 따라 실시간 유효성 검사 (DB에 존재 여부에 따라 문구 출력)
  • 제이쿼리로 중복체크 이후 중복된 값으로 회원가입 시 DB 저장 안 되게 처리

 

7. script.js

$(document).ready(function(){
	// 정규식 변수
	var idJ = /^[a-zA-Z0-9]{4,20}$/;
	
	// 아이디 정규식 체크 및 중복확인
	$('#id_form').on('keyup', function(){
	  	// 한글 입력 안 되게 처리
		$(this).val( $(this).val().replace( /[ㄱ-ㅎ|ㅏ-ㅣ|가-힣]/g, '' ) );
		// 입력한 값의 길이 가져옴
		var id_length = $(this).val().length;
      		// 입력한 값에서 공백 제거 후 id에 넣음
		var id = $('#id_form').val().replace(/\s/gi,'');
			
		// 정규식이 맞지 않거나, 입력값이 0보다 클 때
		if(!idJ.test(id) && id_length >0){
			$('#id_tag').text('특수문자를 제외한 2~10자리여야 합니다.');
			$('#id_tag').css('color','#F63805');
		 // 정규식이 맞고, 입력값이 0보다 클 때
		} else if(idJ.test(id) && id_length >0){
			$.ajax({
				type: 'post', 
				data: id,
				url: '/idCheck',
				contentType: 'application/json; charset=UTF-8',
				success: function(cnt) { 
					if(cnt > 0) {
						$('#id_tag').text('중복된 아이디입니다.');
						$('#id_tag').css('color','#F63805');
					} else {
						$('#id_tag').text('사용 가능한 아이디입니다.');
						$('#id_tag').css('color','#000');
					}     
				}
			});
		} else{
			$('#id_tag').text('');
		}
	});
});

 

  • .replace()는 괄호 안의 앞에는 찾는 문자열, 뒤에는 바꿀 문자열을 넣으면 된다.
  • .test()는 찾는 문자열이 들어있는지, 아닌지를 알려준다.

 

위 코드처럼 제이쿼리 처리까지 하면 아래 검사는 완료된 것으로 이제 한 개만 남았다.

ID 중복체크는 제이쿼리로 입력하는 값에 따라 실시간 유효성 검사 (DB에 존재 여부에 따라 문구 출력)

 

 

8. AbstractValidator.java

package data.validator;
// 임포트 생략

public abstract class AbstractValidator<T> implements Validator {

  @Override
  public boolean supports(Class<?> clazz) {
    return true;
  }

  @Override
  @SuppressWarnings("unchecked")
  public void validate(Object target, Errors errors) {
    try {
      doValidate((T) target, errors);
    } catch (RuntimeException e) {
      throw e;
    }
  }

  protected abstract void doValidate(final T dto, final Errors errors);
}

 

  • Validator를 implements 하여 validate의 구현부 try안에 추상 메서드인 doValidate를 만들어서 검증 로직 클래스를
    별도로 생성하여 doValidate 추상 메서드를 구현하면 된다. (아래 IdCheckValidator.class가 검증 로직 클래스)

 

9. IdCheckValidator.java

package data.validator;

import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import data.dto.MemberDto;
import data.mapper.JoinMapper;

@Component
public class IdCheckValidator extends AbstractValidator<MemberDto> {

  private final JoinMapper mapper;

  public IdCheckValidator(JoinMapper mapper) {
    this.mapper = mapper;
  }

  @Override
  protected void doValidate(MemberDto dto, Errors errors) {

    if (mapper.existsById(dto.getId())) {
      errors.rejectValue("id", "아이디 중복 오류", "중복된 아이디입니다.");
    }
  }
}

 

 

10. JoinMapper 추가

<select id="existsById" parameterType="String" resultType="boolean">
	select if(count(*) > 0, 'true', 'false') from member where id = #{id, jdbcType=VARCHAR}
</select>

 

  • 아이디가 존재하면 count가 0보다 크기 때문에 true를 반환하고 아닐 경우 false를 반환하는 sql문!!

 

여기까지 만들었다면 아래 검사까지 완벽하게 완료된 것이다.

제이쿼리로 중복체크 이후 중복된 값으로 회원가입 시 DB 저장 안 되게 처리

중복된 아이디인 상태로 회원가입 버튼을 클릭해도 화면은 넘어가지 않고 해당 문구가 계속 표시된다.

이후 아이디를 사용 가능한 아이디로 수정을 하면 그때 회원가입이 제대로 된다.

 

[왼쪽 : 입력시, 중복된 상태에서 회원가입 버튼 클릭시 / 오른쪽 : 사용 가능한 아이디일 경우]

 

이 게시글은 아이디 중복체크를 위주로 작성했지만 해당 방법으로 여러 가지 항목에 적용할 수 있다.

 

 

 

※ 만약에 따라하다가 잘 안되거나 궁금한 사항이 있으시면 댓글로 남겨주세요.

※ 해당 게시글은 Springboot + Maven + Mybatis + Jstl를 사용하여 개발하고 있는 프로젝트의 일부입니다.