JSP를 이용한 커뮤니티 게시판 3장 - 회원가입

大 炫 ·2020년 10월 31일
3

jsp게시판

목록 보기
3/9
post-thumbnail

3장에서는 ,,

> 1.session을 이용한 로그인과 회원가입,
2.회원가입시 기본프로필 이미지를 부여, 닉네임과 더불어 수정이 가능하고,
3.회원가입한 유저의 db정보를 삭제시키는 간단한 회원탈퇴,
4.게시판의 기본적인 crud,
5.게시글의 pagination,
6.게시글의 추천수와 최신수에 따라 정렬이 가능한 select option,
7.게시글의 keyword에 해당하는 content 또는 title을 포함하는 검색기능
8.댓글과 대댓글
9.접속된 유저들끼리 주고받을 수 있는 websocket의 채팅

3장에서는 회원가입에대해서 먼저 다루려고한다.
인증방식에서는 세션이 사용됬는데, 세션방식은 이번에 처음 다뤄봤지만 내 취향은 토큰이 더 ..

아무튼 이런 취향차이처럼 jsp또한 작성하는 방식이 취향에따라 꽤 차이가 나던데 이번게시판은 철저하게
web > controller > Action > Service > DAO > DB로 접근하는 사이클을 그대로 따랐다.
비동기방식을 이용하는 경우가 아니라면 말이다!

구조적 공부..

web > controller > Action > Service > DAO > DB

본 이미지는.. 내 공부방식인데 대부분의 공부는 내코드보다는 타인의 코드를 보며 공부를 먼저하게되고 다음으로 내코드에 흡수하며 자기것을 만드는 공부를 하는데
언제나 이렇게 구조적으로 접근하는 방식은 나에게 큰 도움이 되더라 !
이쁜 글씨가 아니기때문에 읽으라는게 아니고 .. 혹시라도 JSP뿐만 아니라 MVC패턴을 이용하는 등등의 많은 상황에서 나같은 초보개발자들은 처음 접근할때 항상 이런 구조를 이해하고 접근하면 좋을것같아서 올려봤다 .. !!

예를 들자면 DRF API를 잠깐 공부할때도 마찬가지였다 !

하루종일 컴퓨터를 마주하다보면 가끔 이런 아날로그 공부방식이 머릿속을 비워내고 다시 채울때 쉽게 들어갈때도 있더라

아무튼 아무튼 !!

web(index.jsp)

가장먼저 실행되는 index.jsp에서

회원가입(join.do)라는 경로를 접속하게되면 제일먼저

controller(BoardController)

BoardController라는 컨트롤러에 접속하게 된다. 왜냐하면
@WebServlet("*.do")라는 어노테이션의 경로는 모든이름에 .do를 붙이고 경로를 이동하면 이곳에 향하기로 했기때문이다. doPost, doGet모두 doProcess라는 메서드에 요청과 응답을 모두 양도 ?(달리 뭐라고 설명할지 몰라서..) 시켜줘서 항상 doProcess가 실행되는데 내용은 이와같다.

차근차근 살펴보자 !
request는 웹이 전송한 요청정보 response 웹브라우저에 보내는 응답정보
RequestURI == request가 담은 URI ex) project경로/index.jsp
contextPath == reqeust가 담은 path ex) index.jsp를 제외한 project경로
command에는 project경로를 제외한 request가 날아온 파일(index.jsp)만 남게되어
command를 비교해서 알맞은 action을 넣어주고 forward에는 해당action의 request와 response를 전달
담긴 forword가 null값이 아닐때
forward.isRedirect()값이 true인 것은 forward에 값이 담기는 과정에서 setPath가 이루어졌다면 forward.isRedirect()는 true
true라면 response.sendRedirect(forward.getPath())
forward.getPath()에 결과값인 URL로 요청을 재전송(sendRedirect의 기능)한다.
false라면 dispatcher를 이용한 제어이동
이라는 결과를 갖게된다

과정을 하나하나 짚어보면
우린 그럼 join.do라는 command를 가지고있는 상태이고
if(command.equals("join.do"))가 참이기때문에 action에는 MemberJoinAction이 담기게된다.

Action(MemberJoinAction이)

여기서 action과 actionForward가 나오는데

보기처럼 action은 그저 actionForward에 반환유형이 excute인 인터페이스일 뿐인데
ActionForward는 어떤 경로와, boolean형의 변수를 get, set하는데

controller에서 command값에 따라 action이 실행되고,
그 action에 request와 response를 매개변수로준 return값을 forward에 담게되고 이때
forward.setPath()가 이루어지는데 이때 forward에 setPath가 이루어지지 않는다면
dispatcher를 이용한 제어이동이 실시
정상적으로forward에 값이 담겼다면 forward.getPath()에 결과값인 URL로 요청을 재전송(sendRedirect의 기능)한다.

그래서 join.do에 접속하게되면 join.jsp라는 view가 그려지게되는건데..
사실 처음보자마자 이해하기 힘들다고 생각한다 ! 몇번을 봤는데도 힘들다면 같은 방식으로 jsp작성하다보면 금방 이해할 수 있을거라고 생각한다!

이제다시

web(join.jsp)

onsubmit이나 oninput, onClick에 달려있는 함수는 이따가 다루고 먼저 큰 틀은
form의 내용을 post방식으로 joinProc.do라는곳으로 보낸다는 흔한 내용이다 !
그럼 controller에서 받겠지 ? .do 니까 ?

controller(BoardController)

앞으로 controller에 대한 설명은 하지않을 생각이다. 경로와 action에 담기는 내용을 제외하면 완벽하게 같은 내용이 반복된다. 가독성을 위해 BoardController와 MemberController, AjaxController 등 다른 내용이 담기는 것들을 깔끔하게 분산시킬 수 있겠으나 나는 그냥 BoardController라는 파일에 다 때려 담았다.
그럼 이제 MemberJoinProcAction이 action에 담기게된다.

Action(MemberJoinProcAction)

form에서 보낸 정보들을 getParameter(name값)에 따라 받아오게되는데 흔히 id값과 name값을 일치시키는데 id값과 name의 차이점은 여기 가 간결하게 잘 설명해놨더라 !
그렇게 get한 것들을 모두 각각의 변수에 담고 그걸 다시 model에다가 set해줘야 한다

MemberVo란 모델은 당연히 만들어 줘야한다 !

MemberVo

getter, setter또한 당연히 만들어 줘야하고,,

이렇게 memberVo에 set된 값들을 이제

service에 joinMember라는 메서드로 넘겨주는 것이다.

Service(BoardService)

간단하게 생각하면 !!
회원가입이 성공 또는 실패라는 boolean형 타입의 메서드인 joinMember는 memberVo라는 매개변수를 다시 dao에 insertMember라는 메서드에 인자로 주게된다.
insertMember가 리턴될때 1(회원가입 성공)을 가지고 온다면 commit(con)이 실행될텐데
commit을 하지않으면 db에 저장이 되지않는다. 그러므로 수동으로 commit을 해주는 모습이다.

이런 service의 역할은 action에서 받은 정보를 dao를통해 db에 저장할때 db를 연결, commit, rollback등 많은 역할을 하지만 그런 '역할'만 하기때문에 controller처럼 반복되는 내용이 많게된다. 다른 메서드에 리턴타입도 다르지만 연결, commit등은 같은 형식으로 실행되기 때문이다.
그럼 service에서는 어찌 db랑 접속한다는걸까 ?

JdbcUtil

가장위 보이는 Context는 이렇다

context.xml

web이나 WebContent라는 폴터 안에는 META-INF와 WEB-INF가 존재할텐데
META-INF안에 context.xml파일을 넣어주고 작성해준 모습이다.
username, password와 url에 자기가 해당하는 mysql에 정보를 넣어주면 준비는 끝난셈이다.

그럼 다시 service에서 dao를 호출할때 해당하는 메서드인 insertMember를 살펴보자

DAO(BoardDAO)

executeUpdate가 구문에러 없이 성공적으로 sql구문이 삽입되고나면 1이 리턴되는데 그값을 1일때 true값 뭐 이런식으로 해서 boolean을 리턴할 수 있겠지만 번거롭다고 생각했고
service.insertMember는 boolean형
dao.insertMember는 int형 이런 차이가 싫다면 맞춰줘도 상관없겠다 !
다시 메서드를 보면 set된 값들이 존재하는 MemberVo vo를 받아와서
sql구문에 insert value값을 (?)로 주어 ?에 해당하는 위치에 setString을 index에 따라 vo.getId, vo.getPwd 등
get해온값들을 value값에 각각 넣어주는 모습이다 !
즉 사용자가 view에서 input에 넣어준 값들이 여기까지 끌고와서 insert문에 value값으로 집어넣어주는데 insert가 성공한다면 우리는 db에 사용자 정보를 저장하고 count값에 1을 담아, 리턴해주어 service로 돌아가 result가 1이되고 commit이 되고 정상적으로 db에 저장되는 구조를 작성해준모습이다.
혹시 여기까지 읽었다면 내가 그린 그림에 회원가입에해당하는 구조를 다시 보자 !

음.. 지금보니까 이미존재하는 회원이라고 적은곳은 회원가입이 실패를 한경우인데
pk를 auto increment로 줘서 구문오류가 아니라면 보통 실패가 없을건데 당시에는
구조에 집중을 하다보니 이런 세세한 부분에서 조금 잘못생각해서 적었다는것을 또 느낀다.

member라는 db의 table내용인데 pk가 sq라는 int형 이기때문에 id, email 뭐 이런것들은 중복검사를 어떻게했을까 ?
여기서는 ajax가 들어갔다.

id, email 중복검사 (Ajax)

web(join.jsp)

우선 크게 3개의 함수가 존재한다.

1.joinSubimit()
2.initCheckId() 또는 initCheckEmail()
3.CheckId() 또는 CheckEmail()

사용자의 행동에따라 3번부터 살펴보는게 좋다고생각한다.

위와같이 중복확인이라는 버튼을 클릭하면 실행되는 button의 onClick인데 함수를 먼저 살펴보자

web(join.jsp) - (script)

ckeckId와 email은 글자 몇글자 바꿔주면 동일한 구조이기때문에 Id만 먼저 살펴보자
함수가 실행되기전 전역변수로 result와 result2라는 값에 0을 부여했다.
각각 result = 0은 id값을 중복검사하고 중복된값이 없을경우 1이라는 값으로 변경,
result2 또한 email을 중복검사한 뒤 중복된값이 없을경우 똑같이 1이라는 값으로 변경한다.
이제 함수가 실행된다면 id에 해당하는 input의 value값을 id라는 변수에 담고, 비어있는지, 20자를 초과하는지, 정규표현식에는 적합한지를 먼저 검사하고 모두 통과한다면 ajax로 값이 보내지게 된다.
ajax는 js에서 비동기통신방식의 대표인데 ..
동기 비동기를 나눠 이해하고 다시 ajax를 이해하는편이 좋을듯 한데 구글에 검색하면 엄청 잘 나오는것으로 안다 !
아무튼 순서대로 돌고있는 사이클의 동기식 jsp에서 ajax를 쓴다는건 그 싸이클에 ajax를 집어넣어서 원하는 데이터를 보내고 받고 하는 비동기통신을 사용한다는 의미이다. method(type)로는

post방식(데이터를 보내는),
get(데이터를 요청하는)

두가지 방식이 존재하는데 post방식이라고 데이터를 보내기만 하는것은 아니라 요청받는 곳(여기 코드로따지자면 action쯤되겠네요)에서 데이터를 성공적으로 받았을때 다시 되돌려주는 데이터또한 받을 수 있으니, 데이터를 주고 선택적으로 받는 방식이라고 생각하면 더 좋을 듯 하다.

ajax의 url은 내가 페이지를 이동한다는 행위a태그에 href="login.do"를 클릭한다고 치자. 그럼페이지 이동이 일어나는데 이런 페이지 이동 없이도 그 url로 접속가능한 방식이 바로 ajax이고 url을 명시해주면 된다.

그리고 데이터 타입을 json이라고 명시한다면 데이터를 주고받을때 무조건
{key값 : value값} 이라는 형태로 주고받아야하는데 이는 데이터를 줄 때와 받을 때 모두의 경우에서 데이터를 제어하기가 훨씬 편해지기때문에 datatype을 명시한다.
사실 id값의 value값은 let id라는 배열도, 어레이도 어레이리스트도 뭣도 아니라 json이라는 형태를 사실 지키면서 보낼 필요는 없었지만(후에 그냥 보낼때도 있던걸로 기억한다.) data를 여러개 보내거나 여러개 받는경우 그 value값을 제어하기 위해서 value에 key값을 명시해줘서 제이터를 쉽게 제어할 수 있게 해준다.
말로 설명하면 좀 어렵지만

ajax 데이터타입이 중요한이유

예를 들어보자 !
지금 보내는 ajax에서 데이터 타입을 빼고 통신을 보내보겠다!
주고받는 원리 이런건 다 생각하지말고 id의 value값인 test123 이라는 id값을 ajax를 통해 보냈고
만약 중복되는 결과가 없다면 count라는값에 0을 중복되는 결과가 존재한다면 count라는 값에 1이라는 값을 받기로했다. 데이터 통신이 성공적으로 이루어져서 받은 data를 console.log에 찍어보면

이라는 결과를 받게된다.
이게 왜 ?라는 반응은 조금만 더 보자.
그러면 데이터타입을 json이라고 명시하고 다시 data를 받아서 log에 찍어보자

그런다음 여기서 우리가 count값으로 받은 0을 제어하는 모습을 보자.
0이라는 값을 들고오기위해 나는 각각 이런 방법을 선택했다.
먼저 json을 명시하지 않았을 때

그리고 json을 명시했을 때

코딩은 재밌는게 항상 같은 결과를 가져오더라도 그 방식에 따라 천차만별이었는데 위와같은 예시또한 같은 결과를 가져오지만 상당히 다른방식을 취하는 좋은 예라고 생각한다.

지금은 count라는 값이 0이라는 하나의 데이터지만 2개 3개만 되더라도 split으로 자르고 substring으로 자르는 행동은.. 음.. 재주껏 해보는게 고생하기 좋을 듯 하다..

뭐 사실 지금같은경우는

받는 data를 json으로 작성해서 받았기때문에 그랬지만

이런식으로 count라는 key값을 제외하고 받는다면 json이라는 데이터 타입을 명시하지않아도
0이라는 값을 쉽게 가져올 수 있지만 , 0이 아니라 다수의 값을 받는경우라면 ?
어느정도의 불편함을 감수하고 해보는것도 나는 이래서 불편하구나 이래서 json형태로 주고받는구나 하면서 깨닫는.. 그런 상황은 충분히 찬성이기때문에 기회가 된다면 해보는것도 나쁘진..나쁘긴한데..

자이제 ajax에대한 설명을 마쳤고 다시 코드로 돌아가보자. 그렇게 우리는 ajax를통해 checkId.do라는 url로 페이지이동없이 접속했고 controller가 반응한다.

Controller(BoardController)

는 다시 action으로

Action(MemberCheckIdAction)

에서는 join.jsp에서 ajax로 보낸 id값을 받아와서 결과적으로db에 그 아이디가 있는지 없는지 조회하기 위해선 또 service를 거쳐 dao로 접근 sql문을 db에 삽입해 넣어야한다. 그 과정은
service.idCheck(id)라는 service의 idCheck라는 메서드를 통한다.

Service(BoardService)


는 다시 dao로 접근

DAO(BoardDAO)


매개변수로받은 id값을 where문에 넣어 as cnt를 통해 member테이블에 중복되는 결과가 있는지 검색한다.
결과값인 cnt를 count에 담아서 return시키고 결국 이값을 우리는 받게되는데 다시 action을 살펴보면 ActionForward로 setPath시켜주는 경로가 join.jsp가 아니다.
AjaxCheckId라는 jsp페이지에 정보를 전달하고, 전달까지 성공했을 때 ajax의 success가 실행, 그럼 AjaxCheckId에서 받은 정보를 매개변수로 받을 수 있게 되었다.
그렇게 받은 data.count를 비교해서 0이라면 사용가능한 아이디라고 나타내고, result에 0값을 넣어주는 것이다.

여기까지왔으면 result2인 email값은 똑~같이 작성하면 된다.

다른점은 setPath부분에 따로 AjaxCheckEmail을 만들지않고 AjaxCheckId.jsp를 재사용해줬다는 정도..

web(join.jsp) - (script)

근데 만약 중복검사를 하고나서 id값과 email을 수정한다면 어떻게될까 ?
중복검사는 아무런 의미가 없어지고 중복되는 결과값이 있던 없던 회원가입을 눌렀을때 db에 저장이되버리게된다. 그걸막기위해 등장한 함수가

1.joinSubimit()
2.initCheckId() 또는 initCheckEmail()
3.CheckId() 또는 CheckEmail()

두번째 함수이다. 내용부터 보자

이 함수는 input태그에 oninput이벤트에 해당되는데 oninput은 쉽게말해서 사용자가 input태그에 뭔가를 입력할때 발생하는 이벤트이다. 즉 중복확인 해놓고 다시 input태그 작성시 실행되는데 0으로 바뀐 result값을 1로 다시 초기화 한뒤 다시 중복검사를 하라는 제스처를 취하는것이다. 그럼 이렇게 result값들은 어디에 쓰이는걸까 ?
바로 form태그가 action으로 정보를 보내기 직전에 onsumbit이 일어나는

1.joinSubimit()
2.initCheckId() 또는 initCheckEmail()
3.CheckId() 또는 CheckEmail()

이부분에 해당한다 바로 살펴보기 전에 이벤트함수를 기입할때

이렇게 return을 명시해줄때도 있고 그렇지 않을때도있는데 이경우는
return문을 넣는 이유는 에러멘트가 발생하고 return이 false로 이루어진다면 다음이벤트가 동작하지 않게되는데 즉 폼이 action으로 전송이되는 경우를 막아버리는 경우에 해당한다
return문을 넣지않게된다면 에러멘트가 발생해도 return값에 상관없이 aciton이 동작해서 데이타가 전송이 되버리기때문에 return문을 넣어준 것이다.

function joinSubmit() {
            var id = $('#id').val();
            var pwd = $('#pwd').val();
            var pwd_confirm = $('#pwd_confirm').val();
            var email = $('#email').val();
            var nick = $('#nick').val();

            if(result==1 && result2==1){
                alert('아이디와 이메일을 중복체크 하세요');
                if($('#id').val()==''){
                    $('#id').focus();
                }
                return false;
            }
            if(result==1){
                alert('아이디 중복체크 하세요');
                if($('#id').val()==''){
                    $('#id').focus();
                }
                return false;
            }
            if(result2==1){
                alert('이메일 중복체크 하세요');
                if($('#email').val()==''){
                    $('#email').focus();
                }
                return false;
            }

            if (!id) {
                alert("아이디를 입력해 주세요.");
                $('#id').focus();
                return false;
            }
            if (!pwd) {
                alert("비밀번호를 입력해 주세요.");
                $('#pwd').focus();
                return false;
            }
            if (!pwd_confirm) {
                alert("비밀번호확인을 입력해 주세요.");
                $('#pwd_confirm').focus();
                return false;
            }
            if (pwd != pwd_confirm) {
                alert("비밀번호가 일치하지 않습니다.");
                $('#pwd').val("");
                $('#pwd_confirm').val("");
                $('#pwd').focus();
                return false;
            }
            if (!email) {
                alert("이메일을 입력해 주세요");
                $('#email').focus();
                return false;
            }
            if (!nick) {
                alert("닉네임을 입력해 주세요");
                $('#nick').focus();
                return false;
            }

            var regExpId = new RegExp("^[a-z0-9]{4,20}$", "g");
            if (regExpId.exec(id) == null) {
                alert("잘못된 아이디 형식입니다.");
                $('#id').val("");
                $('#id').focus();
                return false;
            }

            var regExpPwd = new RegExp("^.{4,30}$", "g");
            if (regExpPwd.exec(pwd) == null) {
                alert("잘못된 비밀번호 형식입니다.");
                $('#pwd').val("");
                $('#pwd_confirm').val("");
                $('#pwd').focus();
                return false;

            }
            var regExpNick = new RegExp("^[a-z가-힣]{4,20}$", "g");
            if (regExpNick.exec(nick) == null) {
                alert("잘못된 닉네임 형식입니다.");
                $('#nick').val("");
                $('#nick').focus();
                return false;
            }
            var regExpEmail = new RegExp("^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*.[a-zA-Z]{2,3}$", "g");
            if (regExpEmail.exec(email) == null) {
                alert("잘못된 이메일 형식입니다.");
                $('#email').val("");
                $('#email').focus();
                return false;
            }
        }

좀 길어서 코드로 첨부했는데 뭐 쉬운내용이라 가독성이 딱히 필요없을 듯 하다.
result값에따라 중복확인 하지않은 값들을 다시 중복확인 해달라는 요청, 빈 값이 있으면 안되고, 정규표현식에 맞는지 뭐 그런걸 살피는 것이다.

사실 정규표현식은 아직 제대로 공부하지않았는데 ㅠ 후에 시간적 여유를 꼭 내서 공부를 해보려고한다.
정규표현식에 해당하는 java파일은 이렇다.

public class RegExp {
    public static final int ARTICLE_NUM = 0; //상수로 사용,switch문에서 숫자로하면 뭐가뭔지 모름
    public static final int ARTICLE_TITLE = 1;
    public static final int ARTICLE_CONTENT = 2;
    public static final int MEMBER_ID = 3;
    public static final int MEMBER_PWD = 4;
    public static final int MEMBER_NICK = 5;
    public static final int MEMBER_EMAIL = 6;
    public static final int IS_NUMBER=7;

    public static final String EXP_ARTICLE_NUM = "[0-9]*$"; //숫자 비교
    public static final String EXP_ARTICLE_TITLE = "^.{1,100}$"; //글 제목 100자까지 인지
    public static final String EXP_ARTICLE_CONTENT = "^.{1,65535}$"; //글 내용
    public static final String EXP_MEMBER_ID = "^[a-z0-9]{4,20}$"; //회원 ID
    public static final String EXP_MEMBER_PWD = "^.{4,30}$"; //회원 비밀번호
    public static final String EXP_MEMBER_NICK = "^[a-z가-힣]{2,20}$";
    public static final String EXP_MEMBER_EMAIL = "^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*.[a-zA-Z]{2,3}$";
    public static final String EXP_IS_NUMBER = "[0-9]*$";

    public static boolean checkString(int type, String data) { //타입,비교할 데이터
        boolean result = false;
        //타입 검사
        switch (type) {
            case ARTICLE_NUM:
                result = Pattern.matches(EXP_ARTICLE_NUM, data);
                break;
            case ARTICLE_TITLE:
                result = Pattern.matches(EXP_ARTICLE_TITLE, data);
                break;
            case ARTICLE_CONTENT:
                result = Pattern.matches(EXP_ARTICLE_CONTENT, data);
                break;
            case MEMBER_ID:
                result = Pattern.matches(EXP_MEMBER_ID, data);
                break;
            case MEMBER_PWD:
                result = Pattern.matches(EXP_MEMBER_PWD, data);
                break;
            case MEMBER_NICK:
                result = Pattern.matches(EXP_MEMBER_NICK, data);
                break;
            case MEMBER_EMAIL:
                result = Pattern.matches(EXP_MEMBER_EMAIL, data);
                break;
            case IS_NUMBER:
                result = Pattern.matches(EXP_IS_NUMBER, data);
                break;
        }
        return result;
    }
}

자 이렇게해서 회원가입이 완성됬다.
생각보다 코드도 설명도 되게 길어졌는데 다음포스팅부터는 간략하게 설명을 이어나가도록 하겠으며 4장에서는 로그인에대해서 살펴보겠다.

++ db정보

profile
대현

0개의 댓글