이번엔 다중 게시판을 구현해보려고 한다.
순서는
먼저 게시판테이블에 BGNO 라는 게시판 구분용 속성을 추가한다.
게시판 테이블은 여러개 만들지 않고, 하나의 테이블을 사용하는 방식으로 구현하려고 한다.
커뮤니티의 BGNO = 1 , Q&A = 2, 포럼 = 3 이런식으로 그룹을 나눠서 구별한다.
새로운 속성이 추가되고, 게시글을 전부 나눠줘야 하기 때문에,
조회, 삽입, 수정 ,삭제 모두 백과 프론트단을 전부 수정해줘야 한다.
일단 게시판테이블(MP_BOARD)에 BGNO를 추가한다.
alter table MP_BOARD add BGNO NUMBER NOT NULL;
UPDATE MP_BOARD SET BGNO = 1;
현재 지금 있는 글들의 BGNO를 1로 갱신해준다.
MAPPER
<select id="listPage" resultType="kr.co.vo.BoardVO"
parameterType="kr.co.vo.SearchCriteria">
SELECT BNO,
TITLE,
CONTENT,
WRITER,
REGDATE,
HIT,
REPLYHIT
FROM (
SELECT BNO,
TITLE,
CONTENT,
WRITER,
REGDATE,
HIT ,
REPLYHIT,
ROW_NUMBER() OVER(ORDER BY
BNO DESC) AS RNUM
FROM MP_BOARD
WHERE 1=1
<include refid="search"></include>
and BGNO=#{bgno} //이부분
) MP
WHERE RNUM BETWEEN #{rowStart} AND #{rowEnd}
ORDER BY BNO DESC
</select>
현재 조회수,댓글수,검색 및 페이징 기능을 포함한 상태의 게시글 목록 mapper.xml 부분이다.
서브쿼리 내에 WHERE 절에 게시판에 따라 입력을 받고, 글들을 구분해서 출력해줄 BGNO를 포함시켜준다.
지금 구현하는 다중게시판 방식은 검색로직과 거의 비슷하다.
검색은 제목,작성자,내용등 어떤 것을 검색하는지 구분한 후 #{keyword}를 기준으로 URL을 만들어서 기능하는데,
((TITLE LIKE '%' || #{keyword} || '%') or (CONTENT LIKE '%' || #{keyword} || '%') or (WRITER LIKE '%' || #{keyword} || )
게시판을 나눠주는 BGNO도 WHERE BGNO=#{bgno} 게시판을 들어가는 링크에 따라 달라지는 bgno를 통해
게시글들을 검색(URL을 만들어주는 작업) 해서 게시판을 나눠줄 수 있다.
그래서 원래의 url은
http://localhost:8080/board/list?page=1&perPageNum=10&searchType=&keyword=
이런식으로 만들어지지만,
여러개로 나눠진 게시판은
커뮤니티 : http://localhost:8080/board/list?page=1&bgno=1&perPageNum=10&searchType=&keyword=
Q & A : http://localhost:8080/board/list?page=1&bgno=2&perPageNum=10&searchType=&keyword=
이렇게 만들어진다.
새로 작성하는 게시글에 bgno를 넣어서 검색기능, 페이징 기능은 그대로 유지하고 게시글만을 나눠줄 수 있다.
VO에 bgno를 추가해주고 getter&setter를 추가해준다. (글 작성용)
private int bgnoinsert;
페이징 기능을 담당해주는 VO에도 bgno를 추가해준다. (검색기능을 구현한 클래스는 상속되어있음) (목록 조회용)
public class Criteria {
private int page;
private int perPageNum;
private int rowStart;
private int rowEnd;
private int bgno;
bgno의 id값이 다른 이유는 뒤에서 설명하겠다
public String makeQuery(int page) {
UriComponents uriComponents = UriComponentsBuilder.newInstance().queryParam("page", page)
.queryParam("perPageNum", cri.getPerPageNum())
.queryParam("bgno", cri.getBgno()).build()
;
return uriComponents.toUriString();
}
// 검색기능
public String makeSearch(int page) {
UriComponents uriComponents = UriComponentsBuilder.newInstance().queryParam("page", page)
.queryParam("perPageNum", cri.getPerPageNum())
.queryParam("searchType", ((SearchCriteria) cri).getSearchType())
.queryParam("keyword", encoding(((SearchCriteria) cri).getKeyword()))
.queryParam("bgno", cri.getBgno()).build()
;
return uriComponents.toUriString();
}
이건 받아온 page의 파라미터들을 기준으로 url을 만들어주는 uriComponenets 부분인데,
bgno가 uri에 추가될 수 있도록 queryParam에 넣어준다.
uriComponents는 지난 포스팅으로 작성했기도 하고, 구글링하면 많이 나오니 자세한 설명은 생략하겠다.
게시판 테이블에 bgno속성을 추가하고 VO에 추가해서 파라미터를 get&set할 수 있도록 해주고,
url을 만드는 부분에 추가시키면 이제 준비는 끝났다.
원래의 url에 /board/list?bgno=1 이런식으로 입력하면 원래의 게시글이 나올 것 이다.
이제 조회 삽입 수정 삭제를 구현하면 된다.
조회)
목록 뷰에 제목을 누르면 상세페이지(게시글 내용)으로 가도록 링크를 맞춰놨는데 ,그 부분을 수정해준다.
<tr>
<th>번호</th>
<th width="600" >제목</th>
<th>작성자</th>
<th>등록일</th>
<th>조회수</th>
</tr>
</thead>
<c:forEach items="${list}" var="list">
<tr>
<td><c:out value="${list.bno}" /></td>
<td ><a href="/board/readView?bno=${list.bno}&
bgno=${scri.bgno}& //이부분
page=${scri.page}&
perPageNum=${scri.perPageNum}&
searchType=${scri.searchType}&
keyword=${scri.keyword}">
<c:out value="${list.title}" /></a> <strong> (${list.replyhit}) </strong> </td>
<td><c:out value="${list.writer}" /></td>
<td><fmt:formatDate value="${list.regdate}"
pattern="yyyy-MM-dd hh:mm" /></td>
<td><c:out value="${list.hit} " /></td>
</tr>
</tbody>
목록 컨트롤러에서 검색,페이징 부분은 @ModelAttribute("scri")로 파라미터를 받고, 결과를 리턴할 수 있게 했놓았다.
그래서 아까 검색부분에 추가한 bgno를 scri.로 그대로 사용한다.
이제 게시글상세보기(게시글내용) 부분이다.
상세페이지의 화면이다.
여기서 수정 삭제 목록 이전글 다음글 버튼의 코드를 조금씩 수정해줘야 한다.
그리고 페이지 이동시마다 url에 bgno값이 잘 넘어오는지 확인하면서 넘어가야한다.
<input type="hidden" id="bgno" name="bgno" value="${scri.bgno}">
bgno의 값을 다음페이지로 넘길 수 있도록 input hidden을 추가해준다.
수정을 하기위해선 수정페이지로 넘어가야하고, 수정페이지에서 수정버튼을 눌러서 수정컨트롤러를 mapping해서 수정로직을 처리한다.
그래서 수정페이지로 가는 url을 바꿔주고, 수정컨트롤러를 수정시켜서 수정 후 redirect시의 url을 수정시켜준다.
// 수정버튼 클릭시
$("#update_btn").on("click", function(){
formObj.attr("action", "/board/updateView");
formObj.attr("method", "get");
formObj.submit();
location.href = "/board/updateView?bno=${read.bno}"
+"&bgno=${scri.bgno}" //추가
+"&page=${scri.page}"
+"&perPageNum=${scri.perPageNum}"
+"&searchType=${scri.searchType}&keyword=${scri.keyword}";
})
(동일하게 수정페이지에서 취소버튼을 누르거나 했을 경우에 돌아오는 url을 수정해준다.)
// 게시판 수정
@RequestMapping(value = "/update", method = RequestMethod.POST)
public String update(BoardVO boardVO,
@ModelAttribute("scri") SearchCriteria scri,
RedirectAttributes rttr,
@RequestParam(value="fileNoDel[]") String[] files,
@RequestParam(value="fileNameDel[]") String[] fileNames,
MultipartHttpServletRequest mpRequest) throws Exception {
logger.info("update");
service.update(boardVO, files, fileNames, mpRequest);
rttr.addAttribute("bno", boardVO.getBno());
rttr.addAttribute("bgno", scri.getBgno()); //이부분
rttr.addAttribute("page", scri.getPage());
rttr.addAttribute("perPageNum", scri.getPerPageNum());
rttr.addAttribute("searchType", scri.getSearchType());
rttr.addAttribute("keyword", scri.getKeyword());
return "redirect:/board/readView";
}
목록버튼에도 "&bgno=${scri.bgno}" 추가해준다.
삭제 컨트롤러 부분에도 rttr.- 동일하게 추가해주면 된다.
삭제, 댓글, 첨부파일등의 기능들은 bno를 기준으로 하기 때문에, 다른 기능들은 손볼 필요가 없다.
카테고리 부분이다. 아직 많이 손보지 않아서 대충 만들었는데,
<li class="nav-item active">
<a class="nav-link" href="/board/list?bgno=1"> <i class="fas fa-fw fa-table"></i> <span>커뮤니티</span></a>
</li>
이렇게 bgno를 붙여서 링크를 걸어준다.
공지사항엔 bgno=2를 붙여줬다.
일단 bgno값을 붙여서 이동시키면 따로 검색어 url을 붙이지않아도, 검색기능을 이용하면 서버를 거치면서 검색url이 붙으니 다른 부분은 포함 시킬 필요가 없다. bgno값은 변동되지않고 계속 유지되도록 만들었으니, 게시판 카테고리를 눌러서 게시판을 변경하지 않으면 bgno값은 변경되지 않는다.
<c:choose >
<c:when test="${scri.bgno == 1}">
<h1 class="h3 mb-2 text-gray-800">커뮤니티</h1>
</c:when>
<c:when test="${scri.bgno == 2}">
<h1 class="h3 mb-2 text-gray-800">공지사항</h1>
</c:when>
</c:choose>
게시판마다 제목이 바뀌도록 bgno값을 기준으로 if문을 걸어준다.
이제 다중게시판 조회, 수정, 삭제가 됐으니 작성부분을 만들어보겠다.
<insert id="insert" parameterType="kr.co.vo.BoardVO"
useGeneratedKeys="true" keyProperty="bno">
<selectKey keyProperty="bno" resultType="int" order="BEFORE">
SELECT
MP_BOARD_SEQ.NEXTVAL FROM DUAL
</selectKey>
INSERT INTO MP_BOARD( BNO
, TITLE
, CONTENT
, WRITER
, BGNO )
VALUES(
#{bno},
#{title},
#{content},
#{writer},
#{bgnoinsert} )
</insert>
글작성 쿼리문에 bgno 값을 추가시켜준다.
<select class="chk" id="bgnoinsert" title="게시판을 선택하세요">
<option value="" selected>게시판을 선택해주세요</option>
<option value="1">커뮤니티</option>
<option value="2">공지사항</option>
</select>
function fn_valiChk(){
var regForm = $("form[name='writeForm'] .chk").length;
for(var i = 0; i<regForm; i++){
if($(".chk").eq(i).val() == "" || $(".chk").eq(i).val() == null){
alert($(".chk").eq(i).attr("title"));
return true;
}
}
}
글작성뷰에서 form입력문 안에 select option이나 드롭다운버튼등을 활용해서 게시판을 선택할 수 있게 만들어준다.
커뮤니티를 선택하면 bgno값으로 1이 들어간다. (option value)
해당 select option문은 form태그 안에 있고, 따로 폼태그와 id값등을 맞추지 않아도 자동으로 상위 폼태그에 매칭된다.
이제 카테고리를 설정 후 , 글 작성을 하고 다시 원래의 카테고리로 돌아가야한다.
글 작성은 글목록 뷰에서 글작성 뷰로 넘어와서 등록버튼을 누르면 등록 컨트롤러를 호출해서 리다이렉트 주소로 다시 돌아오는 구조로 구현했다.
삭제나 목록버튼처럼 bgno값을 목록뷰에서부터 가져와서 글 작성을 완료해도 원래의 카테고리로 돌아가야 한다.
//글작성 페이지
@RequestMapping(value = "/board/writeView", method=RequestMethod.GET)
public String writeView( @ModelAttribute("scri") SearchCriteria scri) throws Exception{
logger.info("writeView");
return "/board/writeView";
}
목록뷰에서 글작성 페이지로 이동할때 scri값을 가져올 수 있도록 ModelAttribute를 추가한다.
<input type="hidden" name="bgno" value="${scri.bgno}"/>
--------------------------------
//글작성
@RequestMapping(value="/board/write", method=RequestMethod.POST)
public String write(BoardVO boardVO,MultipartHttpServletRequest mpRequest, RedirectAttributes rttr,@ModelAttribute("scri")SearchCriteria scri) throws Exception{
logger.info("write");
logger.info("mpRequest");
service.write(boardVO,mpRequest);
rttr.addAttribute("bgno", scri.getBgno());
return "redirect:/board/list";
}
등록버튼을 누를시 scri.bgno값을 같이 제출할 수 있도록 input hidden으로 넣어주고
글작성 컨트롤러에서도 scri를 받아서 리 다이렉트 할 수 있도록 RedirectAtrributes와 @ModelAttribute(scri)를 매개변수에 넣어준다.
그리고 list로 리턴하면 bgno값을 url뒤에 붙여서 목록부분으로 리턴한다
더 많은 게시판을 구현하고 싶으면 작성페이지에서 bgno값을 3 ,4 ,5 계속 늘려서 뷰 부분에서 추가해주면 되겠다.
이제 나중에 회원가입,로그인기능을 구현하고 관리자페이지에서 카테고리를 수정하는 부분도 구현해보려고 한다.
문제++++++
나중에 확인해보니 bgno의 id값을 같은 bgno로 설정해서 카테고리 페이지를 기억하는 scri의 bgno와
글작성할때 사용하는 boardVO의 bgno가 섞여서 제대로 기능하지 않게 됐다.
그래서 BoardVO의 bgno는 bgnoinsert로 변경해주고 BoardVO를 ParameterType으로 사용하는 insert 부분의 쿼리의 bgno의 value를 #{bgnoinset}로 맞췄다.
문제++++
게시글테이블 페이징 부분에서 총 게시글수를 구해서 페이지수를 만드는 부분에서 문제발생,
공지사항 카테고리엔 글이 몇개 없지만 커뮤니티 카테고리의 글들의 수까지 합해서 페이징을 처리해서
공지사항 카테고리에도 글이 없는 페이지가 만들어지고 있었음
--->
<select id="listCount" parameterType="kr.co.vo.SearchCriteria"
resultType="int">
SELECT COUNT(BNO)
FROM MP_BOARD
WHERE 1=1
<include refid="search"></include>
AND BNO > 0
AND BGNO = #{bgno}
</select>
페이징에 필요한 게시글 총개수를 구하는 쿼리의 WHERE문에 BGNO를 추가해서 문제 해결.
+++++++전체 게시판 구현
아주 간단하다.
listPage의 where Bgno=#{bgno} 를
<if test="bgno != 0"> and BGNO=#{bgno} </if>
if test로 감싸서 만들면 된다.
where문의 bgno속성을 없애버리면 전체를 검색하니 bgno 0번을 전체게시판으로 만들어주자.
'SPRING > IceWater Community' 카테고리의 다른 글
[스프링]회원가입(ajax 유효성 검사,비밀번호 암호화) - 로그인1 (0) | 2021.09.10 |
---|---|
[스프링]게시판 조회수,댓글수 순 정렬과 게시글 10개,20개씩 보기 구현 (3) | 2021.09.07 |
[스프링]게시판 이전글 다음글 구현 (5) | 2021.09.03 |
[스프링] ajax 댓글 에디터 및 이미지 삽입 구현 (0) | 2021.09.02 |
[스프링]게시판 글작성 에디터 적용과 파일미리보기,이미지 미리보기 (0) | 2021.08.30 |