Dev/Struts

Struts2 - 답변형 게시판 만들기(2) 게시글조회/답변/수정/삭제

창문닦이 2019. 3. 22. 18:08


1. 테이블 구조

boardNum-기본키 (숫자)

[varchar2] name, pwd, email, subject, content, ipAddr, created

[number] groupNum, depth, orderNo, parent(삭제를 위한 컬럼), hitCount

2. 답글 정렬 알고리즘

1,5 : 동일 그룹 1번  / 2,3,4 동일 그룹 2번 

groupNum은 최상위 parentBoardNum을 의미한다. 

depth는 답변의 심도에 해당한다. parent에 해당하는 레코드의 depth+1값을 반영한다.

orderNo는 같은 그룹안에서 출력되는 순서를 의미한다(desc-내림차순으로 정렬).


① 게시글 A 입력, ② 게시글 B 입력

항상 나중에 입력된 게시글이 위로 올라간다.

parent가 없는 게시물의 경우 depth, orderNo, parent 모두 0으로 입력되고, groupNum도 자기자신의 BoardNum이다.


boardNum

subject

groupNum

depth

orderNo

parent

2

B

2

0

0

0

1

A

1

0

0

0


③ 게시글 B의 답글 BB1 입력

parent는 부모의 BoardNum 에 해당한다. B의 답글이므로 parent는 B게시물의 BoardNum = 2 이다. groupNum은 부모의 groupNum을 가져온다.


boardNum

subject

groupNum

depth

orderNo

parent

2

B

2

0

0

0

3

BB1(B의답글)

2

1(0+1)

1(0+1)

2

1

A

1

0

0

0


④ 게시글 B의 답글 BB2 입력

4번 게시글이 3번 게시글이후로 작성되어서 위에 반영된 것이다.

4번 게시글의 orderNo은 parent의 orderNo+1로 ‘1’이 입력이 되고, 3번 게시글의 OrderNo은 기존 값에서 +1하여 ‘2’로 변경된다.

답변 게시물이 입력되는 경우 OrderNo가 변경되는 기준은 동일한 GroupNum을 가지고 OrderNo이 입력되는 값보다 큰 게시물만 해당된다.


<!-- 답변일 경우 orderNo를 변경 -->

<update id="orderNoUpdate" parameterClass="map">

update bbs set orderNo=orderNo+1

where groupNum=#groupNum# and orderNo>#orderNo#

</update>


boardNum

subject

groupNum

depth

orderNo

parent

2

B

2

0

0

0

4

BB2(B의답글)

2

1(0+1)

1(0+1)

2

3

BB1(B의답글)

2

1

2(1+1)

2

1

A

1

0

0

0


⑤ 게시글 A의 답글 AA 입력

A의 답글이므로 parent는 A게시물의 BoardNum = 1 이다. groupNum은 부모의 groupNum을 가져온다.

boardNum

subject

groupNum

depth

orderNo

parent

2

B

2

0

0

0

4

BB2(B의답글)

2

1

1

2

3

BB1(B의답글)

2

1

2

2

1

A

1

0

0

0

5

AA(A의답글)

1

1(0+1)

1(0+1)

1


⑥ 게시글 BB2의 답글 BBB 입력

6번이 게시글이 입력되면 groupNum는 부모의 groupNum을 써주고 depth는 부모+1

4번게시물 답변이 작성될 때 OrderNo가 동일한 GroupNum(2)을 가지고 OrderNo이 (1)보다 큰 게시물만 해당된다.


(orderNo ▶ 입력된 데이터의 부모 orderNo 보다 큰 데이터들 모두 orderNo+1 반영)


boardNum

subject

groupNum

depth

orderNo

parent

2

B

2

0

0

0

4

BB2(B의답글)

2

1

1

2

6

BBB(BB2의답글)

2

2

2(1+1)

4

3

BB1(B의답글)

2

1

3(2+1)

2

1

A

1

0

0

0

5

AA(A의답글)

1

1

1

1




게시판 URI 정리

 URI

기능

forward

 http://localhost/struts2/bbs/created.action

 게시글 작성

 /board/created.jsp

 http://localhost/struts2/bbs/article.action 

 게시글 조회

 /board/article.jsp

 http://localhost/struts2/bbs/list.action 

 게시판 리스트 조회

 /board/list.jsp

 http://localhost/struts2/bbs/updated.action

 게시글 수정 

 /board/created.jsp

- 수정 완료시 

 list페이지로 redirect

 http://localhost/struts2/bbs/reply.action 

 게시글 답변

 /board/created.jsp

- 답변 완료시

 list페이지로 redirect

 http://localhost/struts2/bbs/deleted.action

 게시글 삭제시

 list페이지로 redirect


게시글 조회 기능

1 .board_sqlMap.xml 작성 (iBatis)

<!-- 조회수 증가 -->

<update id="updateHitCount" parameterClass="int">

update bbs set hitCount = hitCount+1 where boardNum = #boardNum#

</update>

<!-- 한개의 게시물 가져오기 -->

<select id="readData" parameterClass="int" resultClass="boardDTO">

select boardNum,name,pwd,email,subject,content,ipAddr,groupNum,

depth,orderNo,parent,hitCount,created from bbs

where boardNum = #boardNum#

</select>

<!-- 이전글 -->

<select id="preReadData" parameterClass="map" resultClass="boardDTO">

<![CDATA[

select data.* from (

select boardNum, subject from bbs

where ( $searchKey$ like '%' || #searchValue# || '%' ) and

((groupNum>#groupNum#) or (groupNum=#groupNum# and orderNo<#orderNo#))

order by groupNum asc, orderNo desc) data

where rownum=1

]]>

</select>

<!-- 다음글 -->

<select id="nextReadData" parameterClass="map" resultClass="boardDTO">

<![CDATA[

select data.* from (

select boardNum, subject from bbs

where ( $searchKey$ like '%' || #searchValue# || '%' ) and

((groupNum<#groupNum#) or (groupNum=#groupNum# and orderNo>#orderNo#))

order by groupNum desc, orderNo asc) data

where rownum=1

]]>

</select>

2. struts-board.xml 액션 등록 (struts2)

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"

"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>

<package name="board" extends="struts-default" namespace="/bbs" >       

<action name="created" method="created" class="com.board.BoardAction">

<result name="input">/board/created.jsp</result>

<!-- 작성완료시 반환값이 success -->

<result name="success" type="redirectAction">list</result>

</action>

<action name="list" method="list" class="com.board.BoardAction">

<result name="success">/board/list.jsp</result>

</action>

<action name="article" method="article" class="com.board.BoardAction">

<result name="success">/board/article.jsp</result>

</action>

</package>

</struts>

3. Action 클래스의 article 메소드 생성 (Model)

public String article() throws Exception {

HttpServletRequest request = ServletActionContext.getRequest();

CommonDAO dao = CommonDAOImpl.getInstance();

//dto의 get로 만들경우 readData할 때 dto가 초기화되므로 매개변수 값 받고 진행

String searchKey = dto.getSearchKey();

String searchValue = dto.getSearchValue();

String pageNum = dto.getPageNum();

int boardNum =dto.getBoardNum();

//검색값 없을 경우

if(searchValue==null||searchValue.equals("")){

searchKey = "subject";

searchValue = "";

}

//검색값 있을 경우 한글디코딩

if(request.getMethod().equalsIgnoreCase("GET")){

searchValue = URLDecoder.decode(searchValue, "UTF-8");

}

//조회수 증가

dao.updateData("board.updateHitCount",boardNum);

//게시글 읽어오기

dto = (BoardDTO)dao.getReadData("board.readData", boardNum);

if(dto==null)

return "read-error";//게시글 찾을 수 없을 경우 오류메시지 출력

//줄바꿈

int lineSu = dto.getContent().split("\n").length;

dto.setContent(dto.getContent().replaceAll("\n", "<br/>"));

//이전글 다음글

Map<String, Object> hMap = new HashMap<String, Object>();

hMap.put("searchKey", searchKey);

hMap.put("searchValue", searchValue);

hMap.put("groupNum", dto.getGroupNum());

hMap.put("orderNo", dto.getOrderNo());

//이전글 데이터

BoardDTO preDTO = (BoardDTO)dao.getReadData("board.preReadData",hMap);

int preBoardNum = 0;

String preSubject = "";

if(preDTO!=null){

preBoardNum = preDTO.getBoardNum();

preSubject = preDTO.getSubject();

}

//다음글 데이터

BoardDTO nextDTO = (BoardDTO)dao.getReadData("board.nextReadData",hMap);

int nextBoardNum = 0;

String nextSubject = "";

if(nextDTO!=null){

nextBoardNum = nextDTO.getBoardNum();

nextSubject = nextDTO.getSubject();

}

String params = "pageNum=" + pageNum ;

if(!searchValue.equals("")){

params += "&searchKey=" + searchKey;

//한글 보내기 위해 인코딩

params += "&searchValue=" + URLEncoder.encode(searchValue, "UTF-8");

}

//이전글

request.setAttribute("preBoardNum", preBoardNum);

request.setAttribute("preSubject", preSubject);

//다음글

request.setAttribute("nextBoardNum", nextBoardNum);

request.setAttribute("nextSubject", nextSubject);

//페이징

request.setAttribute("params", params);

request.setAttribute("lineSu",lineSu);

request.setAttribute("pageNum",pageNum);

return SUCCESS;

}

4. Article.jsp 생성 (View)

<%@ page contentType="text/html; charset=UTF-8"%>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<%

request.setCharacterEncoding("UTF-8");

String cp = request.getContextPath();

%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

<title>게 시 판</title>

<link rel="stylesheet" href="<%=cp %>/board/css/style.css" type="text/css" />

<link rel="stylesheet" href="<%=cp %>/board/css/article.css" type="text/css" />

<script type="text/javascript">

function sendData(value){

var boardNum = "${dto.boardNum}"; /* Action클래스에서 넘어오는 파라미터값 */

var url = "<%=cp %>/bbs/";

/* 삭제, 수정, 답변 링크 */

if(value=="delete")

url += "deleted.action";

else if(value=="update")

url += "updated.action";

else if(value=="reply")

url += "reply.action";

url += "?boardNum=" + boardNum;

url += "&${params}"

location.replace(url);

}

</script>

</head>

<body>

<div id="bbs">

<div id="bbs_title">

게 시 판(Struts2)

</div>

<div id="bbsArticle">

<div id="bbsArticle_header">

${dto.subject }

</div>

<div class="bbsArticle_bottomLine">

<dl>

<dt>작성자</dt>

<dd>${dto.name }</dd>

<dt>줄수</dt>

<dd>${lineSu }</dd>

</dl>

</div>

<div class="bbsArticle_bottomLine">

<dl>

<dt>등록일</dt>

<dd>${dto.created}</dd>

<dt>조회수</dt>

<dd>${dto.hitCount}</dd>

</dl>

</div>

<div id="bbsArticle_content">

<table width="600" border="0">

<tr>

<td style="padding: 20px 80px 20px 62px;" valign="top" height="200">${dto.content}</td>

</tr>

</table>

</div>

<div class="bbsArticle_bottomLine" >

이전글 :

<c:if test="${!empty preSubject }">

<a href="<%=cp%>/bbs/article.action?${params}&boardNum=${preBoardNum}">

${preSubject }

</a>

</c:if>

</div>

<div class="bbsArticle_bottomLine" >

다음글 :

<c:if test="${!empty nextSubject }">

<a href="<%=cp%>/bbs/article.action?${params}&boardNum=${nextBoardNum}">

${nextSubject }

</a>

</c:if>

</div>

</div>

<div class="bbsArticle_noLine" style="text-align: right;">

from ${dto.ipAddr}

</div>

<div id="bbsArticle_footer">

<div id="leftFooter">

<input type="button" value=" 답변 " class="btn2" onclick="sendData('reply')" />

<input type="button" value=" 수정 " class="btn2" onclick="sendData('update')" />

<input type="button" value=" 삭제 " class="btn2" onclick="sendData('delete')" />

</div>

<div id="rightFooter">

<input type="button" value=" 리스트 " class="btn2"

onclick="javascript:location.href='<%=cp %>/bbs/list.action?${params }';" />

</div>

</div>

</div>

</body>

</html>






게시글 수정/답변기능 (답변 : insert/수정 : update)

1. board_sqlMap.xml SQL문 작성 

<!-- 답변일 경우 orderNo를 변경 -->

<update id="orderNoUpdate" parameterClass="map">

update bbs set orderNo=orderNo+1

where groupNum=#groupNum# and orderNo>#orderNo#

</update>


<!-- 게시글 수정 -->

<update id="updateData" parameterClass="boardDTO">

update bbs set name=#name#, subject=#subject#, email=#email#,

content=#content#,pwd=#pwd# where boardNum=#boardNum#

</update>

2. struts-board.xml 매핑정보 작성

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"

"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>

<package name="board" extends="struts-default" namespace="/bbs" >    

<!-- 게시판작성 -->   

<action name="created" method="created" class="com.board.BoardAction">

<result name="input">/board/created.jsp</result>

<!-- 작성완료시 반환값이 success -->

<result name="success" type="redirectAction">list</result>

</action>

<!-- 게시판리스트 -->

<action name="list" method="list" class="com.board.BoardAction">

<result name="success">/board/list.jsp</result>

</action>

<!-- 게시글조회 -->

<action name="article" method="article" class="com.board.BoardAction">

<result name="success">/board/article.jsp</result>

</action>

<!-- 게시글수정 -->

<action name="updated" method="updated" class="com.board.BoardAction">

<result name="input">/board/created.jsp</result>

<result name="success" type="redirectAction">

list?pageNum=${dto.pageNum}&amp;searchKey=${searchKey}&amp;searchValue=${searchValue}

</result>

</action>

<!-- 게시글답변 -->

<action name="reply" method="reply" class="com.board.BoardAction">

<result name="input">/board/created.jsp</result>

<result name="success" type="redirectAction">

list?pageNum=${dto.pageNum}&amp;searchKey=${searchKey}&amp;searchValue=${searchValue}

</result>

</action>

</package>

</struts>

3. created.jsp 수정 (Mode에 따른 버튼 변경 및 호출 액션 설정)

<script type="text/javascript" >

function sendIt(){

if(f.mode.value=="created"){

f.action = "<%=cp%>/bbs/created.action" ;

}else if(f.mode.value=="updated"){

f.action = "<%=cp%>/bbs/updated.action" ;

}else{

f.action = "<%=cp%>/bbs/reply.action";

}

f.submit();

}

</script>


<div id="bbsCreated_footer">

<!-- update 필요한 파라미터 -->

<input type="hidden" name="boardNum" value="${dto.boardNum }"/>

<input type="hidden" name="pageNum" value="${pageNum }"/>

<!-- reply 필요한 파라미터 -->

<input type="hidden" name="groupNum" value="${dto.groupNum }"/>

<input type="hidden" name="orderNo" value="${dto.orderNo }"/>

<input type="hidden" name="depth" value="${dto.depth }"/>

<input type="hidden" name="parent" value="${dto.boardNum }"/>

<input type="hidden" name="mode" value="${mode }">

<c:if test="${mode=='created' }">

<input type="button" value="등록하기" class="btn2" onclick="sendIt();" />

<input type="reset" value="다시입력" class="btn2"

onclick="document.myForm.subject.focus();" />

<input type="button" value="작성취소" class="btn2"

onclick="javascript:location.href='<%=cp %>/bbs/list.action?${params }';" />

</c:if>

<c:if test="${mode=='updated' }">

<input type="button" value="수정하기" class="btn2" onclick="sendIt();" />

<input type="button" value="수정취소" class="btn2"

onclick="javascript:location.href='<%=cp %>/bbs/list.action?${params}';" />

</c:if>

<c:if test="${mode=='reply' }">

<input type="button" value="답변등록하기" class="btn2" onclick="sendIt();" />

<input type="reset" value="다시입력" class="btn2"

onclick="document.myForm.subject.focus();" />

<input type="button" value="작성취소" class="btn2"

onclick="javascript:location.href='<%=cp %>/bbs/list.action?${params}';" />

</c:if>

</div>


액션클래스의 created메소드

4. Action class updated 메소드 작성

public String updated() throws Exception {

HttpServletRequest request = ServletActionContext.getRequest();

CommonDAO dao = CommonDAOImpl.getInstance();

String searchKey = request.getParameter("searchKey");

String searchValue = request.getParameter("searchValue");

String pageNum = dto.getPageNum();

//검색값 없을 경우

if(searchValue==null||searchValue.equals("")){

searchKey = "subject";

searchValue = "";

}

//검색값 있을 경우 한글디코딩

if(request.getMethod().equalsIgnoreCase("GET")){

searchValue = URLDecoder.decode(searchValue, "UTF-8");

}

String params = "pageNum="+pageNum;

if(!searchValue.equals("")){

params += "&searchKey=" + searchKey;

//한글 보내기 위해 인코딩

params += "&searchValue=" + URLEncoder.encode(searchValue, "UTF-8");

}

request.setAttribute("params", params);

if(dto.getMode()==null||dto.getMode().equals("")){

dto = (BoardDTO)dao.getReadData("board.readData", dto.getBoardNum());

if(dto==null){

return "read-error";

}

request.setAttribute("mode", "updated");

request.setAttribute("pageNum", dto.getPageNum());

return INPUT;

}

dao.updateData("board.updateData", dto);

request.setAttribute("searchKey", searchKey);

request.setAttribute("searchValue", searchValue);

return SUCCESS;

}

5. action클래스 reply 메소드 생성 

public String reply() throws Exception {

HttpServletRequest request = ServletActionContext.getRequest();

CommonDAO dao = CommonDAOImpl.getInstance();

String searchKey = dto.getSearchKey();

String searchValue = dto.getSearchValue();

String pageNum = dto.getPageNum();

//검색값 없을 경우

if(searchValue==null||searchValue.equals("")){

searchKey = "subject";

searchValue = "";

}

//검색값 있을 경우 한글디코딩

if(request.getMethod().equalsIgnoreCase("GET")){

searchValue = URLDecoder.decode(searchValue, "UTF-8");

}

String params = "pageNum="+pageNum;

if(!searchValue.equals("")){

params += "&searchKey=" + searchKey;

//한글 보내기 위해 인코딩

params += "&searchValue=" + URLEncoder.encode(searchValue, "UTF-8");

}

request.setAttribute("params", params);

request.setAttribute("searchKey", searchKey);

request.setAttribute("searchValue", searchValue);

//답변창

if(dto==null||dto.getMode()==null||dto.getMode().equals("")){

dto = (BoardDTO)dao.getReadData("board.readData", dto.getBoardNum());

if(dto==null)

return "read-error";

//엔터

String temp = "\r\n\r\n--------------------------------------------\r\n\r\n" ;

temp += "[답변]\r\n";

//원문이 위에 오고 답변이 기재되도록

dto.setSubject("[답변]" + dto.getSubject());

dto.setContent(dto.getContent() +  temp);

//재작성되는 내용 초기화

dto.setName("");

dto.setEmail("");

dto.setPwd("");

request.setAttribute("mode", "reply");

request.setAttribute("pageNum", dto.getPageNum());

return INPUT;

}

//답변작성

//orderNo 변경

Map<String,Object> hMap = new HashMap<String, Object>();

hMap.put("groupNum", dto.getGroupNum());

hMap.put("orderNo",dto.getOrderNo());

//SQL문 실행

dao.updateData("board.orderNoUpdate", hMap);

//답변입력

int maxBoardNum = dao.getIntValue("board.maxBoardNum");

dto.setBoardNum(maxBoardNum+1);

dto.setIpAddr(request.getRemoteAddr());

//답변이 밑에 달리도록

dto.setDepth(dto.getDepth()+1);

dto.setOrderNo(dto.getOrderNo()+1);

dao.insertData("board.insertData", dto);

return SUCCESS;

}





계층형 DB를 활용한 삭제

select boardNum,parent,subject from bbs

where boardNum in

(select boardNum from bbs start with boardNum=7

connect by prior boardNum=parent)

order by boardNum;


계층형 구조를 활용하여 parent 게시글(boardNum=7)을 삭제할 경우

7번 게시글을 parent로 하는 8번 게시글 삭제

8번 게시글을 parent로 하는 9번 게시글 삭제

8번 게시글을 parent로 하는 11번 게시글 삭제


 1. sqlMap 등록 

해당 boardNum을 지우면 계층형 쿼리를 이용하여 parent에 동일한 boardNum이 있으면 함께 삭제

<!-- 게시글 삭제 -->

<delete id="deleteData" parameterClass="int">

delete bbs

where boardNum in

(select boardNum from bbs start with boardNum=#boardNum#

connect by prior boardNum=parent)

</delete>

2. Struts-board.xml 에 redirect 정보 등록

<!-- 게시글삭제 -->

<action name="deleted" method="deleted" class="com.board.BoardAction">

<result name="success" type="redirectAction">

list?pageNum=${dto.pageNum}&amp;searchKey=${searchKey}&amp;searchValue=${searchValue}

</result>

</action>

3. BoardAction deleted 메소드 생성

public String deleted() throws Exception {

CommonDAO dao = CommonDAOImpl.getInstance();

dao.deleteData("board.deleteData",dto.getBoardNum());

return SUCCESS;

}