1.session을 이용한 로그인과 회원가입,
2.회원가입시 기본프로필 이미지를 부여, 닉네임과 더불어 수정이 가능하고,
3.회원가입한 유저의 db정보를 삭제시키는 간단한 회원탈퇴,
4.게시판의 기본적인 crud,
5.게시글의 pagination,
6.게시글의 추천수와 최신수에 따라 정렬이 가능한 select option,
7.게시글의 keyword에 해당하는 content 또는 title을 포함하는 검색기능
8.댓글과 대댓글
9.접속된 유저들끼리 주고받을 수 있는 websocket의 채팅
4,5,6,7에 해당하는 모든 내용을 한번에 다루려고한다.
최초에는 게시판 기본적인 crud이후 페이지네이션, select option에따라 정렬 등 순차적으로 작성을 하려고 했으나, 글이 매끄럽지 못하여 우선은 이렇게 한꺼번에 정리하려고한다.
이렇게 작은 게시판 안에도 얼마나 생각할게 많고 해야할것도 많은지 !
위의사진에 head부분에 게시판이라는 a태그는 이렇게 이루어져있다.
board.do? 이후에 있는
pn은 pagination
val은 최신순, 추천순으로 정렬하기 위한 value값 기본값이 newest이기때문에 val=newest이다.
filter는 위와같이 검색기능에 전체, 제목, 내용으로 검색할수있게 하는 filter이고, 여기선 기본값이 null인데 이는 action에서 null값일때는 전체로 적용되게 했으니
이는 val값 같은경우는 내가 설계했고, filter와 keywird같은경우는 파트너가 설계했으니 정말 취향에따라 이런 세세한 부분들이 다르더라.
그리고 input에 입력하는 값들은 keyword가 된다.
이렇게 기본값들이 먼저 정해져 board페이지가 열릴때 getParameter값으로 전달해줘야하기 때문에 url에 이와같이 전달해주는 모습이다.
그럼 이렇게 열린 board.do는
그렇게 url로 보내준 pn, val, filter, keyword를 받아주는 모습이고
아까 얘기했듯 filter값이 없을 경우 all로 인식하게끔, keyword를 적는 input에 값을 입력했을때만 query라는 변수에 makeSearchQuery가 filter와 keyword를 매개변수로 가지게되는 메서드가 실행되는것이다. 메서드는 밑에서 설명하겠다.
이부분은 사실 마음에 들지않는 코드이지만 ㅠ 내 능력으로는 더이상 깔끔하게 리팩토링 할 수가 없었다..
val값과 filter,keyword값에 따른 정렬이 두가지 모두 충족시켜야할 경우의 수가 있기때문에
(예를들면 추천순 정렬에서 filter는 "all" keyword는 "test"라는 검색을 할때라는 경우)
이렇게 조금 난잡하게 경우의 수를 적게되었다ㅠ
마지막으로 각각의 저장된 val, filter, keyword를 다시 보내주기위해 setAttribute해주는 모습이고
아까 위에서 설명안한 makeSearchQuery라는 메서드는 이렇게 filter와 keyword를 인자로 받아 검색에 알맞은 query문을 생성하여 리턴해주는 함수이다.
왜이렇게 생겼는지는.. 쿼리문을 삽입할때 where ~ 해줘야하는 부분에 ~에 해당하는 곳이다.
그리고 service로 넘어가 dao에서 리턴되는 값들을 가져오는 부분이 이정도있는데
맨위의 pagenation은 최초 val값이나 filter, keyword값이 null값이거나 기본값일때 현재페이지를 보여주는 pagenation이고,
filterPagenation은 기본값이 아닌 keyword가 입력되어 query가 null값이 아닐때 현재페이지를 알려주는 pagenation이다.
둘을 따로 분리한 이유는 분리하지않으면 filter를 먹였을 때 페이지가 넘어가면 filter가 초기화되며 페이지가 변경되던데 ,, 내 수준으로는 따로 분리해서 작업할 수 있었는데 혹시 더 좋은 의견있다면 시도하고싶다!
그리고 뿐만아니라 나뉜것은 불러오는 ArticleList또한 상황에 따라 나눴는데,, val의 기본값 "newest"일때 getArticleList(), val가 "best"이라면 getBestArticleList(), 동일하게 query문이 null값이 아니고 val값이 "newest"일때와 "best"일때 각각 필터링된 결과물을 view에 그릴 수 있도록 각각의 상황에 맞게 setAttribute해주는 모습이었다.
각각을 모두 살펴보기전에 Pagenation은 다음과 같다.
getter, setter는 생략,,
눈여겨볼 부분은 SHOW_ARTICLE_COUNT = 10;
PAGE_GROUP = 5;
정도라고 생각하고 넘어갔다.
각각에 해당하는 service에서 dao를 호출해주는 모습인데 인자값, 반환형, 등 사소한 부분 말고는 역시나 항상 같은 모습을 유지하는 모습
여기서 pagenation 또는 filterPagenation에 들어갈 값들이 정해지는데
pagenation은 전체 board의 개수, 즉 게시물의 총 개수를 리턴하고
filterPagenation은 입력된 query문에 해당하는 게시물의 총 개수를 리턴한다.
이 리턴된 값은 많은 부분에서 이용되는데 각각의 상황에 맞을때 살펴보겠다.
게시판에 들어왔을때 가장기본적인 val값이 기본값 즉 "newest"일때 불러오는 articleList이다.
아까 pagenation할때 눈여겨볼점 중
SHOW_ARTICLE_COUNT = 10; 은 limit부분에 ?, ? 두번째 물음표에 해당값이 들어가게되는데
이는 limit 시작위치, 반환개수 로 현재페이지에따라 view에서 보여지는 article을 조절해주는 부분이 이부분이다.
첫번째 물음표에 해당하는 getStartArticleNumber()은
현재페이지가 1페이지일 때 limit 0, 10
2페이지일 때 limit 10, 10
3페이지일 때 limit 20, 10 으로 페이지가 변경될때마다 시작위치를 변경해주는 부분이다.
이로써 해당페이지에 해당하는 article만을 불러올 수 있게 되는것이다.
이는 거의 같은 맥락인데 order by 부분이 likeCount desc라는 점말고는 완벽히 동일하니 val값이 "best"일때 해당하는 dao부분이다.
getFilterArticle부터는 인자로 query문이 작성된 String값을 받는데 역시 검색결과에 따른 값만을 보여주기 위해 where + query + 문을 제외하고는 똑같은 부분이다.
요즘은 이런 기본적인 틀은 먼저 구글링해서 나오는 틀을 익힌다면
인자로 뭘 더주고 그에따라 조금씩 변형시키면서 내가 원하는 작업을 수월하게 할 때
공부가 되는 느낌이다. 코딩을 시작한지 얼마안됬을때는 모든 문장을 하나하나 이해하려고했는데
이해의 부분과 알고넘기는 부분을 분리하다보니 실력이 빨리 상승되는것같은데..
어차피 사실 이렇게 넘기는 부분도 후에는 다시 공부해야할 숙제겠지만.. 그때하는 공부와 지금하는 공부는 시간적 효율을 봤을 때..
현재 아직 신입으로 일도 못해본 나로서는 이렇게 효율을 뽑아내는게 맞지않나 싶다..
잡생각은 여기까지하고 ,,
역시나 같은 구조의 val값이 "best"인 경우에 해당하는 dao이다.
이렇게 setAttribute할 값들이 모두 정해지고 forward.setPath("/views/board.jsp")가 이루어질때 다시 view로 돌아가 그려지는 그림들을 받으려면 jsp페이지에서도 getAttribute해주고 그에 따른 값들을 for문 if문 등으로 그려줘야한다.
위와같이 action > service > dao > service > action 으로 다시 세팅된 값이 web으로 보내지면 web(.jsp)에서는 setAttribute된 값을 getAttribute함으로써, 또 해당하는 자리에 적절히 for문, if문을 통해 세팅될때 마침내 view에는 우리가 원하던 그림들이 그려지게된다.
물론
각각의 pagenation이 현재페이지가 1페이지일때 1에 active라는 class를 추가해주어 현재페이지를 구분할 수 있도록 해주는 함수와,
select의 option값이 변경될때마다 form의 action에 해당하는 val값을 url로 넣어주고 submit을 해주는데
이렇게 action을 항상 새로 초기화시켜서 넣어주지않으면 함수가 실행될때 val값이 null값이 잡히게되어서 이렇게 함수를 작성하게 됬다.
또 검색에 해당하는 이런 함수가 다 포함되야 정상적인 기능이 실행될것이다.
이제 작성에 해당하는 create를 살펴보자.
이렇게 board.jsp에서 글쓰기 버튼을 클릭하게되면 똑같이 controller ~
이렇게 간단한 로그인여부만을 확인하고 board-write.jsp를 접속하는 모습이다.
제목과 내용이 null값은 아닌지 확인하는 간단한 함수와 글쓰기의 form이 보이는데, 딱히 특이사항이랄건 없고 footer에 등록이라는 글쓰기완료버튼을 누르게되면 기본타입이 submit이기때문에 onsubmit이 먼저 실행되고 return을 명시하고있기 때문에 checkData가 return을 false하게되면 action이 실행되지 않고 정상적으로 checkData가 실행되었을 때 action이 실행되어 boardRegister.do로 input과 textarea의 value값들을 를 전송하게된다.
기본적인 로그인검사와 글 유효성 검사, board-write.jsp에서 get해온 title과 content를 각각의 변수에 담는 모습이고
글작성시 필요한 title, content를 제외한 user의 정보를 가져오기 위해 member의 sq를 가져오는 getMemberSequence()와 member의 프로필 img를 가져와 vo에 set해주는 모습이다.
기본이미지를 basic.jpg라고 설정해놨기때문에 getMemberImg()가 null값을 가져왔을 때를 대비해서 한번더 set해주는 모습이다.
이제 vo에 set된 내용으로 service를 통해 insertArticle()을 실행시킨다.
우선 board에 해당하는 db정보를 먼저 보자.
사실 설계부터 차근차근 한게 아니라 중간중간 오타와 이런 조그만 오류가 존재하는데
어떤 오류냐면..
본래 m_sq를 제외한 다른 자질구레한 nickName라던지를 db정보에 포함하지 않은 이유가
글저장, 불러오기 등의 과정에서 m_sq에 해당하는 nickName과 profileImg를 불러오는 방법이 있고,
글작성시 nickName과 profileImg를 db정보에 넣는 방법도 있는데
무슨차이냐면 글 작성하고 작성자가 프로필변경시 변경된 값이 이미 작성된 글에 반영이 되냐 안되냐의 차이이다.
예를 들자면 내 경우 오류라는 이유가
이렇게 댓글도 글 작성도 같은사람이 했는데 각각 다른 profileImg와 nickName을 가지고 있었을 때 작성했지만 profileImg는 다른데 nickName은 같은 이유가 이런 이유이다..
고치려면 여기저기 댓글 대댓글 여기저기 조금씩 손봐야해서.. 그냥 작은 오류라고 생각하고 방치해버렸다..
아무튼 이렇게 글 작성이 완료가되면
db에 저장되는 모습과 실제 게시판에서 불러올 수 있는 모습이다.
이제 실제 제목을 클릭했을 때 detail페이지에 접속하기 위해
그리고 접속시
ArrayList인 list 와 ReList는 댓글과 대댓글이기때문에 후에 다루도록하고,
articleDetail에해당하는 vo만 먼저 다루도록 하겠다.
detail페이지에 접속할때 url에 포함하는 ?num= 값을 get해주어 매개변수로 넣어준 모습이고
인자로 받은 detail num에 해당하는 게시글의 정보를 inner join으로 vo에 set해주는 모습이다.
그렇게 set해준 vo는 return되어
이렇게 다시 vo로 받아와 글쓴이와 접속유저의 id가 일치하면 수정, 삭제버튼이 보이게끔 해주는 모습이다.
먼저 비교적 간단한 수정버튼을 눌렀을 때 수정되는 과정부터 살펴보자.
그렇게 다시 돌아간 jsp페이지는
다시 이렇게 받아와서 내가 작성했던 input과 textarea의 value값을
요로코롬 가져올 수 있다.
이제 등록버튼이라는 글 수정버튼을 클릭했을 때
지금보니까 BoardUpdateProcAction으로 이름을 지었어야 했는데
생각없이 복사붙여넣기 하다보면 이렇게 어색한 이름이나 오타가 있을 수 있는데 ..
그만큼 이정도 결과물만 만들어냈다면 코드중 복사 붙어넣기가 80~90프로 비중이 차지된다...
이렇게 완성이다.
다시 web으로 돌아가 삭제기능을 살펴보자.
이제 삭제버튼을 클릭하면
이렇게 기본적인
c : create
r : read
u : update
d : delet
완성이 되었고
마지막으로 추천기능을 살펴보자.
좋아요 기능은 ajax를 이용해서 구현했는데 보통 페이지 이동없이 클릭했을때 바로 결과가 나오기 위해서는 ajax를 많이 쓴다하더라!
이렇게 반응형으로 크기에따라 달라지는 velog의 디자인을 그대로 들고왔다.
이렇게 같은 이름의 class로 세팅해준뒤에
btnLike라는 class를 가진 놈을 클릭했을 때는 추천수가 1올라가게, 만약 추천이 이미 되있다면 -1이 되게끔 설계해놨는데 db정보는 아래와같다.
해당글의 sq와 해당 유저의 sq를 저장함으로써 유저가 그 게시물에 추천을 했는지 안했는지를 구분하는 식이다.
그래서 보내는 data는 해당 게시글의 sq를 보내야하는데, 이렇게 post로 보내는 url에 접속하게되면
service에 접근하는 첫번째 service.recCount(no)부터 알아보자
recCount(no)는 현재 게시글에 총 추천수를 먼저 구하는 함수이고
recCheck(no, m_sq)는 현재 게시글에 유저가 추천을 했는지 안했는지 검사하는 함수인데 이를 반환형을 boolen으로, 만약 추천을 안했다면 true로 추천이 이미 되있다면 false를 리턴해준다.
true 일때 실행되는 recUpdate(no, m_sq)는 해당 게시글의 sq와 유저 sq저장시키고
updateBoardRec(count + 1, no)는 board에 해당하는 db의 likeCount를 1증가시켜준다.
false일때는 이미 추천이 되있기때문에 recDelete(no, m_sq) 로 저장된 sq들을 삭제시키고
updateBoardRec(count - 1, no)로 board에 해당하는 db의 likeCount를 1감소시키는 모습이다.
이렇게 해당글에 해당하는 sq와 m_sq를 저장또는 삭제가 가능해졌다면 이제는 board-detail.jsp에서 likeCount를 받아오고 추천했다면
추천하지 않았다면
이렇게 구분까지도 필요할 것이다.
이렇게 recCount() 함수는 추천수를 불러오는 함수라서 jsp페이지 로딩시 바로실행이 된다.
마찬가지로 data는 해당 게시글의 sq를 보낸다.
이렇게 총 추천수와 0또는 1을 setAttribute하는데 onOff가 어떤역할을 하는지는 jsp페이지에서 살펴보자.
사실 주고받을때 json으로 주고받는다면 이렇게 따로 jsp페이지를 작성하지않고
success 시 함수가 받는 data또한 data.count 또는 data.onOff로 쉽게 받을 수 있을텐데
이렇게 받은 data는 jsp에서 받을 수 있는데
이렇게 작성해준다면 JsonData.count , JsonData.onOff를 통해 똑같이 사용할 수 있다.
실제 console.log를 찍어줘도 이렇게 받을 수 있다.
실제 객체를 주고받는거처럼 사용할 수 있다!
그렇게 onOff가 0이라면 아직 추천하지 않았기때문에 색을 넣어주지 않았고
추천이 된다면 color를 내가 원하는 색으로 표시할 수 있게끔 색을 넣어줬다 !
그리고 board.jsp에서도 추천수가 드러나게하려면
똑같이 이렇게 class명만 조금만 수정해주면 불러올 수 있다!
기본적인 crud, 추천기능, 검색filter와 select option에 따른 정렬, 페이지네이션까지 알아봤는데 다음 7장에서는 댓글 대댓글에대해서 살펴보려고한다.
6장에서는 과할정도로 controller와 service, dao를 많이 분리했는데, 분리하여 구조를 익히고, 비슷한 코드가 반복되면서 구조를 더 깊이 이해하는게 jsp에서는 정말 중요한 듯 하여 이렇게 포스팅 할 수밖에 없었다.