본 페이지에서는 게시글 목록만 정리할 예정이다. 게시판목록과 댓글 등은 아래 링크를 확인하세요!
게시판만들기(1) - 게시글 저장하기 : https://isjiji.tistory.com/50
Toy-project 개발일지 ▲NO.12 ▲ 게시판 만들기(1) - 게시글 저장, Spring-boot, Oracle, Java, JavaScript, HTML, CS
STUHEL은 학원생들을 위한 어플이니, 정보를 공유할 수 있는 게시판이 필요하다. 본 페이지에서는 게시글 저장하는 것만 정리할 예정이다. 게시판목록과 댓글 등은 아래 링크를 확인하세요! 게시
isjiji.tistory.com
게시판만들기(2) - 게시글 목록/ 게시글 검색 : https://isjiji.tistory.com/53
Toy-project 개발일지 ▲NO.14 ▲ 게시판 만들기(2) - 게시글 목록 만들기 / Spring-boot, Oracle, Java, JavaScrip
게시판 목록을 만들 때 고민이 많았다. 1. 목록을 담을 틀 - 처음에 agGrid를 사용해서 grid에 게시판 목록을 담았다. 코드는 단순하고 깔끔했다. 하지만 페이징이 들어가면 난잡할 것 같았다. 그리
isjiji.tistory.com
게시판만들기(3) - 댓글,대댓글달기 / 저장된 게시글 확인 :
게시판 목록을 만들 때 고민이 많았다.
1. 목록을 담을 틀
- 처음에 agGrid를 사용해서 grid에 게시판 목록을 담았다. 코드는 단순하고 깔끔했다. 하지만 페이징이 들어가면 난잡할 것 같았다. 그리고 웹페이지에서 목록만 어울리지 않는 분위기를 풍겼다.
- 결국 table태그와 tr, td 태그를 사용해 목록을 다시 만들었다. 테이블 사이즈 조절, 글자크기 조절을 여러번 반복하며 수정해서 조금 불편했다. 하지만 보기에는 훨씬 편하고 이질감없이 잘어울렸다.
2. 페이징
- 페이징을 ◀ 1 2 3 4 5 ▶ 이렇게 숫자로 페이지를 이동하려했으나 페북이나 인스타그램처럼 쭉쭉 내려가면서 볼 수 있는 방식을 사용했다. 이유는 심플하니까! 심플한건 언제나 좋다.
- 하지만 글이 많아질 경우를 대비해 숫자페이징도 같이 해주는게 좋을것 같다는 생각도 들었다. 그렇게 생각하다보니, 특정 시간이 지난 후엔 자동 삭제되는 게시판을 만드는건 어떨까? 생각이 들었다. 그냥 잡담 나누기 좋게끔. 이부분은 아직 결정을 못해서 원상태유지중이다.
3. 검색
- 게시판의 꽃은 검색이쥬 ~? 게다가 숫자페이징도 없으니, 키워드만 기억해서 게시글을 검색할 수 있게 만들었다.
- 검색범위는 제목, 내용, 작성자, 분류명 네가지이다. '나무'를 검색하면 나무라는 단어가 들어간 제목, 내용, 작성자, 분류명을 전부 가져온다.
- 검색순서를 어떻게 하면 좋을지.. 고민을 하다 게시글의 seq_num 이 큰게(가장최근에 쓴글) 위에 뜨게 만들었다. 단순!!
그러나 얼마전 구글이 처음에 만들어진 이유가 검색엔진 때문이라는 글을 읽었다. 효율적인 게시글을 띄우고 찾기 위해서..! 나 또한 효율적인 게시글 작성을 위해 검색순서를 어떻게 하는게 더 편리할지 더 고민해본 뒤 수정이 필요할듯하다.
4. 한번에 받아오는 게시글 갯수
- 처음엔, 한번 조회할 때 모든 글을 받아오게 만들었다. 그런데 어떤 블로그의 '게시글 만들기'에 누군가 단 댓글을 읽고 조회할 때마다 일정한 수의 게시글을 받아오게끔 변경했다. 댓글을 단 사람은 '적은 게시글이 있을 때는 속도에 영향을 미치지 않겠지만 게시글이 10000개 100000개 될 상황을 생각해서 꼭! 게시글을 분할해서 조회하고 뿌려줘야한다'고 했다.
일단.. 결과물
어떻게 저떻게 결국은...!! 만들어진 게시판 목록페이지 두두둥장!

아래는 처음만들었던 agGrid게시판. 촌스럽스 촌스러우니까 작게보세요

게시판목록/조회
게시판 목록 html
table태그(boardListTable)에 들어갈 tr, td태그는 목록데이터가 들어오면 JavaScript로 생성되게 했다. 기본적으로 목록을 10개씩 띄우지만, 10개가 안될 때(검색할 때 또는 마지막 조회시) 10개 미만의 태그들이 만들어지고 또 게시글 수를 특정할 수 없기 때문이다.
<section id="list">
<div style="display: flex; justify-content: center;">
<div style="width:1250px;position:relative" >
<div align="right">
<input type="button" id="boardWriterBtn" value="글쓰기" onclick="location.href='/stuhel/board/boardWrite.html'" class="btn btn-primary btn-xl" style="margin:7px;font-size:15px;padding:11px 22px;text-align:center">
</div>
<div align="center;margin:10px,0px;">
<hr>
<table style="text-align:center;align:center; font-size:22px;">
<tr>
<td style="width:200px; text-align:center;">작성자</td>
<td style="width:850px; text-align:center;">제목</td>
<td style="width:130px; text-align:center;">조회</td>
<td style="width:150px; text-align:center;">주제</td>
<td style="width:150px; text-align:center;">작성일</td>
</tr>
</table>
<hr>
<table id="boardListTable" style="text-align:center;align:center;font-size:17px;padding:3px">
</table>
<div align="center">
<input type="button" id="retrieveMoreListBtn" value="더보기 ▼" onclick="retrieveMoreList()" class="btn btn-light btn-xl" style="width:900px;margin:20px;padding:14px 450px;font-size:15px;text-align:center;">
</div>
<hr>
<div align="center" id="noMoreList" style="margin:45px;font-size:24px;"></div>
</div>
</div>
</div>
</section>
화면 onload → 게시글 조회 JavaScript
화면이 load되자마자 게시글을 조회한다. maxNum과 minNum은 불러올 게시글의 범위를 정하는 변수이고 첫 조회이기 때문에 1과 10을 주었다.
function retrieveBoardList(){
maxNum=10;
minNum=1;
let xhr = new XMLHttpRequest();
xhr.open('GET', '/stuhel/board/retrieveBoardList?minNum='+minNum+'&maxNum='+maxNum
,true)
xhr.setRequestHeader('Accept', 'application/json');
xhr.send();
xhr.onreadystatechange = () => {
if (xhr.readyState == 4 && xhr.status == 200) {
boardList = xhr.responseText;
boardList = JSON.parse(boardList);
console.log(boardList);
setBoardList(boardList);
}
}
}
게시글 조회 Controller
TO에 minNum, maxNum담아서 서비스단으로 고고
@GetMapping("/retrieveBoardList")
ArrayList<BoardTO> retrieveBoardList(HttpServletRequest request,@RequestParam("minNum")int minNum, @RequestParam("maxNum")int maxNum){
HttpSession session = request.getSession();
BoardTO boardTO = new BoardTO();
boardTO.setMaxNum(maxNum);
boardTO.setMinNum(minNum);
ArrayList<BoardTO> board=boardService.retrieveBoardList(boardTO);
return board;
}
게시글 조회 Service
서비스단에서는 Mapper실행.
받아올 값은 ArrayList<TO> 로 받는데, 여러개의 로우를 받아올 것이기 때문에 ArrayList에 담았다.
public ArrayList<BoardTO> retrieveBoardList(BoardTO boardTO) {
ArrayList<BoardTO>board= boardMapper.selectBoardList(boardTO);
return board;
}
게시글 조회 Mapper
Mapper에서 <![CDATA[ ]]> 를 사용한 이유는 where절에 있는 크기비교 <,> 기호 때문이다.
마이바티스는 태그를 <, > 기호로 사용하기 때문에 쿼리에 해당기호가 들어가면 오류가 발생한다.
그래서 CDATA 안에 들어가는 문자는 모두 문자열로 인식될 수 있게 만들었다.
여기 쿼리에서 한가지 어려운점이 발생했었다.
게시판 조회시, 10개씩 데이터를 겹치지 않고 가져오려면 매번 동일한 정렬이 이뤄지고 난 다음 원하는 갯수만큼가져와야했다. 그래서 모든 데이터를 NOTE_SEQ으로 순서정렬 한 다음 결정된 ROWNUM에 minNum과 maxNum을 비교해 원하는 데이터를 겹치지 않고 받아오게 만들었다. 그래서 서브쿼리가 두번 들어갔다.
1. order by로 정렬 2. rownum정해주기 3. rownum과 비교하여 필요한 범위의 데이터 가져오기
<select id="selectBoardList" resultMap="Board" parameterType="com.helper.study.stuhel.board.to.BoardTO">
<![CDATA[
SELECT
TITLE, NOTE_SEQ, WRITE_DATE, TOPIC_NM, CLICK_AMOUNT, WRITER, R_NUM, TOTAL_LIST_CNT
FROM(SELECT
B.*,
ROWNUM AS R_NUM
FROM(SELECT TITLE, NOTE_SEQ, WRITE_DATE, TOPIC_NM, CLICK_AMOUNT, WRITER, COUNT(*) OVER() AS TOTAL_LIST_CNT
FROM BOARD
ORDER BY NOTE_SEQ DESC ) B )
WHERE R_NUM <= #{maxNum}
AND R_NUM >= #{minNum}
]]>
</select>
키워드검색
키워드 검색 HTML
<header class="masthead" id="about">
<div class="container px-4 px-lg-5 h-100">
<div class="row gx-4 gx-lg-5 h-100 align-items-center justify-content-center text-center">
<div class="col-lg-8 align-self-center">
<h1 class="text-white font-weight-bold">게시판</h1>
<hr class="divider" />
<div align="center">
<input type="text" id="boardSearchTxt" class="form-control" placeholder="키워드를 입력하세요" onkeyup="enterkeyEvent()" style="width:40%;display:inline; ">
<input type="button" id="boardSearchBtn" value="검색" onclick="retrieveBoardKeyword()" class="btn btn-primary btn-xl" style="font-size:15px;padding:11px 22px;text-align:center">
</div>
</div>
</div>
</div>
</header>
키워드검색 JavaScript
먼저 키워드 검색창에 입력값이 있는지 확인한다. 입력값이 없으면 일반조회를 호출한다.
입력값이 있으면 kMaxNum 과 kMinNum에 각각 10, 1 값을 넣어준다. 이 데이터는 정렬된 테이블의 rownum과 비교해 겹치지않는 게시글을 가져오게해줄아이다.
그 다음 이미 조회되어있는 게시글들을 모조리 지워준다. 그래야 검색키워드에 맞는 결과만 얻을 수 있다.
table태그의 row를 삭제하는 deleteRow()를 사용했다.
게시글들을 지운다음 keyWord와 kMaxNum, kMinNum을 Controller에 보낸다.
지금보니 if문 비교 값을 현재검색창 value로 할 것이 아니라, 검색후에 value를 변수에 저장한 다음 해당 변수로 if문 조사를 해야할듯하다. 왜냐? 검색한 적은 없고, 입력만 해놓은 상태에서 더보기 버튼을 누를수도 있으니까!
function retrieveBoardKeyword(){
if(boardSearchTxt.value==null||boardSearchTxt.value==''){
retrieveBoardList();
return;
}
kMaxNum=10;
kMinNum=1;
maxNum=0;
minNum=0;
//화면에 이미 조회되어있는 게시글들을 지워준다.
let boardListTableRows = boardListTable.rows.length-1;
if(boardListTableRows>0){
for(i=boardListTableRows;i>=0;i--){
boardListTable.deleteRow(i);
}
}
let xhr = new XMLHttpRequest();
xhr.open('GET', "/stuhel/board/retrieveBoardKeyword?fullKeyword="+boardSearchTxt.value+"&minNum="+kMinNum+"&maxNum="+kMaxNum
,true);
xhr.setRequestHeader('Accept', 'application/json');
xhr.send();
xhr.onreadystatechange = () => {
if (xhr.readyState == 4 && xhr.status == 200) {
boardList = xhr.responseText;
boardList = JSON.parse(boardList);
console.log(boardList);
setBoardList(boardList);
location.href="#list";
}
}
}
키워드검색 Controller
파라메터로 보낸데이터 받아서 Service에게로 ~
@GetMapping("/retrieveBoardKeyword")
ArrayList<BoardTO> retrieveBoardKeyword(HttpServletRequest request,@RequestParam("fullKeyword")String fullKeyword, @RequestParam("minNum")int minNum, @RequestParam("maxNum")int maxNum){
HashMap<String, Object> map = new HashMap<>();
map.put("maxNum",maxNum);
map.put("minNum",minNum);
map.put("fullKeyword", fullKeyword);
ArrayList<BoardTO> board = boardService.retrieveBoardKeyword(map);
return board;
}
키워드검색 Service
키워드를 띄워쓰기 별로 잘라서 keywordList를 만들어준다. 그렇게 분리된 단어들을 Mapper로 보낸다.
@Override
public ArrayList<BoardTO> retrieveBoardKeyword(HashMap<String, Object> map) {
String[] keywordList = ((String)map.get("fullKeyword")).split(" ");
map.put("keywordList", keywordList);
ArrayList<BoardTO> board = boardMapper.selectBoardKeyword(map);
return board;
}
키워드검색 Mapper
조회방법의 큰 틀은 위에 먼저 기술된 일반조회와 동일하다.
다른것은 foreach태그들이 map에 담겨운 키워드리스트를 검색한다는 점이다.
keywordList에 담긴 keyword는 전부 title, writer, note, topic_nm과 LIKE 조사를 받게되고 동일한 단어를 가지고있으면 해당 단어를 가진 게시글데이터들이 반환된다.
<select id="selectBoardKeyword" parameterType="map" resultMap="Board">
<![CDATA[
SELECT
TITLE, NOTE_SEQ, WRITE_DATE, TOPIC_NM, CLICK_AMOUNT, WRITER, R_NUM, TOTAL_LIST_CNT
FROM(SELECT
B.*,
ROWNUM AS R_NUM
FROM(
]]>
SELECT
TITLE, NOTE_SEQ, WRITE_DATE, TOPIC_NM, CLICK_AMOUNT, WRITER, COUNT(*) OVER() AS TOTAL_LIST_CNT
FROM BOARD
WHERE TITLE LIKE
<foreach collection="keywordList" item="keyword" separator=" OR TITLE LIKE ">
'%'||#{keyword}||'%'
</foreach>
OR NOTE LIKE
<foreach collection="keywordList" item="keyword" separator=" OR NOTE LIKE ">
'%'||#{keyword}||'%'
</foreach>
OR WRITER LIKE
<foreach collection="keywordList" item="keyword" separator=" OR WRITER LIKE ">
'%'||#{keyword}||'%'
</foreach>
OR TOPIC_NM LIKE
<foreach collection="keywordList" item="keyword" separator=" OR TOPIC_NM LIKE ">
'%'||#{keyword}||'%'
</foreach>
<![CDATA[
ORDER BY NOTE_SEQ DESC ) B )
WHERE R_NUM <= #{maxNum}
AND R_NUM >= #{minNum}
]]>
</select>
더보기 클릭
더보기 버튼 클릭 JavaScript
더보기 버튼을 클릭하면 기존의 maxNum과 minNum에 10씩 더해진다. 왜냐? 기존에 가져온 데이터보다 rownum이 10씩 더 큰 데이터만 가져와야 이미 게시된 글들과 겹치지 않기때문에.
kMaxNum과 kMinNum은 검색어입력조회시 사용하기 때문에 검색어가 있는지 없는지 확인한 다음 검색어가 있으면 kMaxNum과 kMinNum이 10씩 더해진다.
이후 Controller Service Mapper는 로직은 위에있는 onload조회, keyword조회와 동일하다.
if-else안에 있는 내용들이 비슷해서 내용을 줄여줘야겠다.고 생각했는데.. 으아니............. 심하게 같은 내용이 반복되고있다....... 그냥 조회할때와 더보기조회할때 로직이 거의 똑같다.... 줄여줘~~~~~~~~~~~~~~~
//더보기 버튼 클릭 --게시목록 추가
function retrieveMoreList(){
if(boardSearchTxt.value==null||boardSearchTxt.value==''){
maxNum=maxNum+10;
minNum=minNum+10;
kMaxNum=0;
kMinNum=0;
let xhr = new XMLHttpRequest();
xhr.open('GET', '/stuhel/board/retrieveBoardList?minNum='+minNum+'&maxNum='+maxNum
,true)
xhr.setRequestHeader('Accept', 'application/json');
xhr.send();
xhr.onreadystatechange = () => {
if (xhr.readyState == 4 && xhr.status == 200) {
boardList = xhr.responseText;
boardList = JSON.parse(boardList);
console.log(boardList);
setBoardList(boardList);
}
}
}else{
kMaxNum=kMaxNum+10;
kMinNum=kMinNum+10;
maxNum=0;
minNum=0;
let xhr = new XMLHttpRequest();
xhr.open('GET', "/stuhel/board/retrieveBoardKeyword?fullKeyword="+boardSearchTxt.value+"&minNum="+kMinNum+"&maxNum="+kMaxNum
,true);
xhr.setRequestHeader('Accept', 'application/json');
xhr.send();
xhr.onreadystatechange = () => {
if (xhr.readyState == 4 && xhr.status == 200) {
boardList = xhr.responseText;
boardList = JSON.parse(boardList);
console.log(boardList);
setBoardList(boardList);
location.href="#list";
}
}
}
}