4/22/2018

(구버전) 스프링 게시판 만들기 #16. 아이디 중복검사 기능 구현

'(구버전) 스프링 게시판 만들기'는 내용이 부족하다고 판단하여
스프링 게시판 만들기를 새로 작성하였습니다.

링크 및 참조용으로 현재 게시물은 남겨두겠지만,
가급적이면 새로운 스프링 게시판 만들기를 참조해주시기 바랍니다.

이전에 구현한 회원가입 기능은 문제점이 있습니다.

먼저, 새로운 아이디로 가입을 합니다.

가입이 완료되었으면, 다시 한번 같은 아이디로 가입합니다.

이렇게 같은 아이디로 가입하게되면 에러가 발생합니다.

처음 회원 테이블을 생성했을 때 primary key(userId) 명령을 이용하여 userId 컬럼을 기본키로 제약 설정했습니다. 기본키는 값이 null이 아니면서 중복되지 않은 고유한 값을 지닙니다.

userName 또한 다른 값과 중복되지 않도록 유니크(unique)로 제약 설정되어있습니다.

이렇게 제약 설정이 적용된 테이블은 중복된 값을 입력하게되면 에러가 발생하므로, 테이블에 값을 넘겨주기 전에 먼저 중복된 값이 있는지 확인을 해야합니다.

먼저, 사용자가 입력한 아이디가 테이블에 있는지 검색하는 쿼리를 작성합니다.

쿼리가 정상적으로 작동되면, 매퍼에 추가합니다.

<!-- 아이디 확인 -->
<select id="idCheck" resultType="com.kuzuro.domain.MemberVO">
 select userId from myMember
     where userId = #{userId}
</select>

DAO와 Service에 추가합니다.

아직 데이터 가공등의 추가 작업은 없으므로 간단합니다.

컨트롤러에 아이디 확인용 메서드를 작성합니다.

아이디 중복검사는 다른 페이지로 이동하지 않고, 가입 페이지(/member/register)에서만 작동합니다. 페이지 이동이된다면, 기존에 입력했던 내용들이 모두 없어지기 때문입니다.

열심히 입력한 정보들이 깨끗하게 사라져버리면 기분이 좋지 않겠지요.

그렇기 때문에 비동기 데이터 전송 기술인 에이젝스(Ajax)가 필요합니다. 에이젝스가 있으면 현재 페이지를 유지한 상태에서, 특정 부분의 데이터만 주고받는게 가능하기 때문입니다.

대표적으로 아이디 중복검사 처럼, 페이지 이동을 하게되면 다시 입력해야하거나 입력한 값을 저장할 추가적인 작업을 할 필요가 없이 에이젝스면 끝나니 간편하지요. 또한 페이지 전체를 새로 불러오는것보다, 필요한 부분만 불러오는것이기 때문에 데이터 전송량이 적으며, 데이터 전송량이 적다면 속도가 빠릅니다. 데이터 전송량이 적으면 그만큼 비용이 줄어들기도하죠.

// 회원 확인
@ResponseBody
@RequestMapping(value = "/idCheck", method = RequestMethod.POST)
public int postIdCheck(HttpServletRequest req) throws Exception {
 logger.info("post idCheck");
 
 String userId = req.getParameter("userId");
 MemberVO idCheck =  service.idCheck(userId);
 
 int result = 0;
 
 if(idCheck != null) {
  result = 1;
 } 
 
 return result;
}

에이젝스를 사용하면 jsp파일이 필요없기 때문에, 메서드 앞에 어노테이션 @ResponseBody 를 추가합니다.

HttpServletRequest 를 이용하여 jsp에서 넘어온 값을 받고, 이 값을 이용하여 Service와 DAO, 그리고 매퍼를 통하여 테이블에 접속해 결과를 받아올 수 있습니다.

결과가 있다면 입력한 내용과 테이블에 있는 내용이 일치하는것이니 중복되는것이고, 결과가 없다면(null) 입력한 내용이 테이블에 없는것이니 중복되지 않는 아이디입니다.

이 결과를 if문으로 구분짓고, 정수형 변수 result를 이용하여 최종 결과를 보냅니다. 이 결과는 해당 에이젝스 메서드를 호출했던 주소로 전송됩니다.

register.jsp에 제이쿼리 CDN을 추가합니다.

<script src='https://code.jquery.com/jquery-3.3.1.min.js'></script>
아이디 확인을 위한 버튼과, 메시지를 보여줄 문단태그 <p>...</p> 를 추가합니다.

그리고 </body> 위에 에이젝스 코드를 추가합니다.

<script> 
$(".idCheck").click(function(){
 
 var query = {userId : $("#userId").val()};
 
 $.ajax({
  url : "/member/idCheck",
  type : "post",
  data : query,
  success : function(data) {
  
   if(data == 1) {
    $(".result .msg").text("사용 불가");
    $(".result .msg").attr("style", "color:#f00");    
   } else {
    $(".result .msg").text("사용 가능");
    $(".result .msg").attr("style", "color:#00f");
   }
  }
 });  // ajax 끝
});
</script>

아이디 확인 버튼(idCheck)이 클릭되면, 아이디가 userId인 요소의 값을 변수 query에 저장합니다. 이 때 저장하는 타입은 일반적인 변수가 아닌 제이슨(JSON)형식의 변수입니다.

제이슨 형식의 변수는 {key:value, key:value ... } 의 형태를 가진 변수입니다. 가독성이 좋고 XML에 비해 용량이 적기 떄문에 데이터를 주고받기 용이합니다.

지금은 아이디만 확인하기 때문에 굳이 제이슨을 사용할 필요없이 일반적인 문자열 변수를 사용해도 전혀 상관이 없습니다.

url은 전송되는 주소이며, 컨트롤러에 생성한 메서드의 매핑 주소를 입력하면 되고 data는 전송할 값을 넣어줍니다.

해당 url로 정상적인 통신이 이루어졌다면 seccess 부분이 실행됩니다. seccess : function(data){} 에서, data에 에이젝스가 전송된 메서드의 결과가 반환됩니다.

제이슨을 사용할 경우, pom.xml에 관련된 라이브러리를 추가할 필요가 있습니다.

pom.xml의  <dependencies>...</dependencies> 사이에 아래의 코드를 추가합니다.

<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<!-- 제이슨 -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.6</version>
</dependency>

이 라이브러리를 추가하지 않으면 제이슨 형식을 사용할 수 없으니 꼭 추가해야합니다.

프로젝트를 실행하고, 이미 가입되어있는 아이디를 입력한 다음 아이디 확인 버튼을 눌렀을 경우, 위 처럼 빨간색의 사용불가 메시지가 출력됩니다.

중복되지 않는 새로운 아이디를 입력한 뒤 아이디 확인 버튼을 누르면 파란색의 사용 가능 메시지가 출력됩니다.

하지만 메시지만 출력될 뿐이지 중복 메시지가 출력된 상태에서 가입이 안되는것도 아닙니다.

처음 회원가입 페이지에 접속했을 때는 아이디에 아무것도 입력하지 않았으므로, disabled="disabled" 속성을 이용하여 가입 버튼을 비활성화 시켜줍니다. 또한 이후 제어를 용이하게 하기 위해 id="submit" 속성을 추가합니다.

에이젝스의 success 내부 if문에 코드를 추가합니다.

$("#submit").attr("disabled", "disabled");
$("#submit").removeAttr("disabled");

아이디가 중복될 경우 id="submit" 속성을 가진 요소는 비활성화(disabled)시켜주고, 아이디가 중복되지 않을 경우 id="submit" 속성을 가진 요소의 비활성화 속성을 제거, 즉 활성화시켜줍니다.

처음 회원가입 페이지에 접속하면 가입 버튼이 비활성화 되어있습니다.

아이디가 중복되지 않다면 가입 버튼이 활성화됩니다.

아이디가 중복된다면 가입 버튼이 비활성화됩니다.

이것으로 회원가입시 회원확인 버튼을 눌러 아이디가 중복되지 않아야만 가입할 수 있게 되었습니다. 하지만 에러가 발생하는 경우가 하나 더 있습니다.

중복되지 않는 아이디로 아이디 확인을 하여 버튼을 활성화 시킨 뒤, 그대로 아이디를 수정한 다음 가입하는 방법입니다.

굳이 저런 방법을할까 싶지만, 에러가 발생한다면 이를 해결해야합니다.

아이디가 userId인 인풋박스에 입력이 발생하면 실행되는 스크립트를 추가합니다.

$("#userId").keyup(function(){
 $(".result .msg").text("아이디를 확인해주십시오.");
 $(".result .msg").attr("style", "color:#000");
 
 $("#submit").attr("disabled", "disabled");
 
});

입력이 발생하게되면 지정한 메시지를 출력하고, 가입 버튼을 다시 비활성화 시켜주는 스크립트입니다.

중복되지 않는 아이디를 입력한 뒤 아이디 확인 버튼을 클릭하면, 가입 버튼이 활성화 됩니다.

아이디 인풋박스에서 입력이 발생하게되면 가입 버튼이 비활성화됩니다. 닉네임도 아이디와 같은 방법으로 중복되는 값을 막을 수 있습니다.

게시물 수정
  1. if(data == 1) {
    $(".result .msg").text("사용 불가");
    $(".result .msg").attr("style", "color:#f00");
    } else {
    $(".result .msg").text("사용 가능");
    $(".result .msg").attr("style", "color:#00f");

    사용가능 사용불가 바뀐거같아요

    답글삭제
    답글
    1. 결과를 보니 바뀐게 아니네요..
      제가 이해하기론 idcheck 의 컨트롤러가 result 값이 쿼리에서 체크를해봤을때 이미 값이 있으면
      초기화값 0이 반환되고 가입이 불가능한거고
      , 값이 없으면 1이 반환되고 있는아이디가 아니니까 가입이 가능한거잖아요?

      그렇게생각하고 ajax

      success: function(data) {
      if(data=1) 이게 사용 가능이어야하고

      1값이 아니면 가입이 불가능이라고 떠야하는거아닌가요??

      function(data) 가 반환값이 result 가 아닌건가요?

      삭제
    2. 안녕하세요?

      먼저 컨트롤러에서는 중복값의 유무에 대해
      int result = 0;
      if(idCheck != null) { result = 1; }

      로 처리합니다.

      idcheck의 값이 null이 아니라면, 즉 값이 있다면 중복이므로 result의 값을 1로 하고
      idcheck의 값이 null이라면, 즉 값이 없다면 중복이 아니므로 if문을 실행하지 못하고 result의 초기값 0으로 전송됩니다.

      이제 뷰단의 에이젝스(Ajax)에서는
      success : function(data) {
      if(data == 1) { $(".result .msg").text("사용 불가"); }
      else { $(".result .msg").text("사용 가능"); }
      }

      이렇게 처기하는데, 여기서 data는 컨트롤러의 result값이 들어오게 됩니다.

      컨트롤러에서 데이터 베이스에 값이 있다면(중복) 1, 아니라면 0으로 했으므로 컨트롤러에서 받아온 data의 값이 1이라면 중복으로 처리하게 됩니다.

      삭제
  2. 아하 컨트롤러의 리턴을 애이잭스 데이터에 넣는거엿군요 감사합니다

    답글삭제
  3. 그러면 var query 변수 를전달하는 데이터 와 sucesss:function(data)은 다른 data인것인가요?

    답글삭제
    답글
    1. $.ajax({
      url : "/member/idCheck",
      type : "post",
      data : query,
      (생략)


      여기서 data는 에이젝스(Ajax)가 해당 URL로 보내는 데이터를 의미합니다. 이 data 문자는 바꿀 수 없습니다. 그리고 sucess : function(data) 에서 data는 에이젝스의 결과값이 저장되는 변수입니다.

      그러므로 sucess : function(result) 또는 sucess : function(a) 처럼 사용자가 마음대로 변경할 수 있습니다.

      삭제
  4. 안녕하세요

    제가 userId 0글자 넣은상태에서도 아이디 확인을 누르면 사용가능이 뜨는게 좀 불편해서
    기본적인코드를 추가로 넣었는데 작동이 안되네요... 이리저리 고쳐봣는데 계속 실행이 잘안되네요..

    form에 name을 form1 로 걸어주고,
    function formCheck(){
    if(document.form1.userId.value==""){
    alert("제목을 입력해주세요");
    return false;
    } return true;
    }
    스크립트에 이정도만 추가했습니다,.

    답글삭제
    답글
    1. 안녕하세요?

      먼저 게시물이 미완성으로 끝나서 이런저런 문제가 있다보니.. 죄송합니다ㅠㅠ

      댓글에 작성하신 코드만 추가하신거라면, 해당 코드는 실행자체가 되지 않습니다. 왜냐하면 formCheck() 함수를 선언만하고 호출하지 않았으니까요. 함수를 호출하는 코드는 var query = {userId : $("#userId").val()}; 위에 넣으시면 됩니다.

      하지만 이미 userId의 값은 변수 query에 저장된 상태이므로, 굳이 새로운 함수를 사용할 필요 없이 에이젝스(Ajax) 코드 전체를 if문으로 감싸주시면 해결됩니다.

      if(query != "") {
      [ 에이젝스 코드 ]
      } else {
      alert("아이디 입력을 해주세요.");
      }


      이런식으로요.

      삭제
  5. 안녕하세요

    말씀하신대로 에이잭스를 if 문 안에 집어넣었습니다.

    if(query !=null){

    $.ajax({
    url : "/member/idCheck",
    type : "post",
    data : query,
    success : function(data) {

    if(data == 1) {
    $(".result .msg").text("사용 불가");
    $(".result .msg").attr("style", "color:#f00");
    $("#submit").attr("disabled","disabled");
    } else {
    $(".result .msg").text("사용 가능");
    $(".result .msg").attr("style", "color:#00f");
    $("#submit").reomoveAttr("disabled");
    }
    }
    }); // ajax 끝

    }else {
    alert("아이디를 입력해주세요");
    }
    });

    하지만 query 값이 null임에도 else문이 실행이 되질 않네요 .

    답글삭제
    답글
    1. 자바스크립트는 조금 독특한 부분이 있는데, 그중 하나가 '비어있는 값'입니다.

      제가 위에 적어둔 코드를 보시면 null로 하지 않고 빈값인 ""(공백이 없는 쌍따옴표 두개)로 했는데, 인풋박스에 아무것도 입력하지 않으면 null이 아니라 빈값을 반환하기 때문입니다.

      실제로 query를 콘솔로그로 출력해보면, null이 아니라 '아무것도 없는 줄'이 출력됩니다.

      자바스크립트에서 주의해야하는것들 중 하나가 null, undefined, "" 입니다.

      이 세가지는 같아보일것 같아도 전혀 다릅니다.

      좀 더 쉽게 설명하기 위해 컵으로 예를들자면.. 자바스크립트에서
      null : 컵 자체가 없음
      undefined : 컵은 있는데 용도를 안정했음
      "" : 컵이 비어있음

      이렇게 생각하시면 될...겁니다:)

      삭제
  6. 안녕하세요

    스크립트 에 다시

    if(query != ""){


    $.ajax({
    url : "/member/idCheck",
    type : "post",
    data : query,
    success : function(data) {

    if(data == 1) {
    $(".result .msg").text("사용 불가");
    $(".result .msg").attr("style", "color:#f00");
    $("#submit").attr("disabled","disabled");
    } else {
    $(".result .msg").text("사용 가능");
    $(".result .msg").attr("style", "color:#00f");
    $("#submit").removeAttr("disabled");
    }
    }
    }); // ajax 끝

    } else {
    alert("아이디를 입력해주세요");
    }
    });

    이렇게 하고 userid 공백인 상태로 idCheck 버튼을 누르면 else문을 안타고
    if(query !=="") 여기가 실행이 되어버리네요

    답글삭제
    답글
    1. 앗 죄송합니다. query가 일반 변수라면 제가 작성한 내용의 유효한데, 현재 query가 제이슨 타입이라는걸 깜빡하고 있었네요, 정말 죄송합니다.

      조건문을 if(query.userId.length) 이렇게 하신다면, 의도하신대로 동작할겁니다.

      query는 제이슨 데이터라서, 값이 없어도 키가 존재하기 때문에 빈값("")일수가 없습니다.
      query.userId로 query의 userId에 접근을 할 수 있으며, query.userId.length로 userId의 길이를 알 수 있습니다.

      userId를 입력하지 않으면 길이가 없으므로(undefined) false를 반납하게됩니다. 반대로 userId를 입력하면 길이가 존재하므로 (1, 2, 3 ...) true가 반납됩니다.

      이 방법을 이용하면, 스페이스도 입력한것으로 인식하기 때문에, userId의 값에 trim을 이용하여 공백을 제거할 필요가 있겠습니다.

      또한 inputrequired 옵션을 부여하여, 클라이언트단에서도 필수적으로 입력할 수 있도록 조치하면 더욱 좋겠지요.

      삭제
  7. service와 dao에서 타입이 memberVO 라고 작성하셧는데 void나 string이 아닌 클래스가 타입인 경우는 어떤경우인가요??

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      리턴 타입이 void인 경우는 리턴 할 데이터가 없는 경우
      리턴 타입이 String인 경우는 리턴 할 데이터가 문자열(String)인 경우입니다.

      삭제
  8. 고수님들.. 도와주십시오 제발 ㅠ 아이디확인 버튼을 눌러도 아무런 일이 일어나지 않습니다.. 뭐가 문제일까요

    답글삭제
  9. kuzuro님 어찌된 영문인지 아이디확인 버튼을 누르면 그냥 모든 값이 사용가능하다고 뜨네요.. 왜 그런간가요

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      확인 버튼을 눌렀을때 값이 db까지 제대로 전달되는지 확인해보셔야할 것 같습니다.
      디버깅을 하시는걸 권장드리고, 디버깅에 익숙하지 않으시다면 컨트롤러에서 받은 값을 System.out.println으로 출력해보세요.

      삭제
    2. 익명7/12/2022

      저도 똑같이 따라해보고 있는데 모든 값이 사용 가능하다고 뜨네요.. 로그로 검사해본 결과 MemberController.java에서 String userId = req.getParameter("userId"); 값을 받아오지 못하고 idCheck가 Null이 되버려서 result가 0인 상태가 되서 넘어가서 그런 것 같습니다. 값을 제대로 받아오려면 어떻게 해야할까요?

      삭제
    3. 안녕하세요? 방문해주셔서 감사합니다.

      프론트에서 가져온 값이 백(컨트롤러)에서 정상적으로 나오는지 확인이 필요로 합니다.

      삭제