기본 게시판 만들기
- 답변 기능 추가하기
답변 기능 추가하기
[ 구현 목표 ]
- 게시판 리스트 항목에서 글 하나를 선택 후 답변을 달 수 있다.
- 글 > 답변 > 답변 > 답변 > ... 이런식으로 계속 답변을 달 수 있다.
- 게시판 글의 답변 번호는 보이지 않게 한다.
[ SQL-Developer - tblBoards DB테이블 수정하기 ]
- 기존 테이블에서 thread, depth 컬럼을 추가해야 하므로 편의상 drop후 진행 하였다.
- thread: 정렬 기준이며 일종의 seq(고유번호) 역할
- depth: 일종의 탭 기능 역할 (0, 1, 2, 3 ...)
drop table tblComment;
drop sequence seqComment;
drop table tblBoards;
drop sequence seqBoards;
create table tblBoards (
seq number primary key, -- 글번호(PK)
id varchar2(30) not null references tblUsers(id), -- 아이디(FK)
subject varchar2(500) not null, -- 제목
content varchar2(400) not null, -- 내용
regdate date default sysdate not null, -- 작성시각
readcount number default 0 not null, -- 조회수
tag varchar2(1) not null check(tag in ('y', 'n')), -- 글내용에 HTML 태그 허용 유무
thread number not null, -- 정렬 기준
depth number not null -- 출력
);
create sequence seqBoards;
[ Servlet + JSP 작업 ]
1) com.test.myapp.board > Add.java
- 데이터 가져오기 ( reply, thread, depth )
- JSP 호출하기 + 데이터 전달하기
@WebServlet("/board/add.do")
public class Add extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
CheckMember cm = new CheckMember();
cm.check(req, resp);
// 1.
String reply = req.getParameter("reply");
String thread = req.getParameter("thread");
String depth = req.getParameter("depth");
// 2.
req.setAttribute("reply", reply);
req.setAttribute("thread", thread);
req.setAttribute("depth", depth);
RequestDispatcher dispatcher = req.getRequestDispatcher("/WEB-INF/views/board/add.jsp");
dispatcher.forward(req, resp);
}
}
2) WEB-INF > views > board > add.jsp
- 글쓰는 페이지(add.jsp)에서 reply, thread, depth를 히든 태그로 넘기기
<!-- addok.do로 POST하는 form 태그 안에 마지막 줄에 넣을것 !! -->
<input type="hidden" name="reply" value="${ reply }" />
<input type="hidden" name="thread" value="${ thread }" />
<input type="hidden" name="depth" value="${ depth }" />
[ DB 작업 ]
1) com.test.myapp.board > BoardDAO.java
- AddOk.java 서블릿이 가장 큰 thread 값을 알려달라고 요청할때 부르는 메소드
public int getMaxThread() {
try {
// nullvalue = nvl 사용해서 쿼리작성
// -> 안하면 그냥 null
// -> 하면 1000
String sql = "select nvl(max(thread), 0) + 1000 as thread from tblBoards";
stat = conn.createStatement();
rs = stat.executeQuery(sql);
if ( rs.next() ) {
return rs.getInt("thread");
}
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
- AddOk.java 서블릿이 답변 글쓰기에 필요한 업무를 위임할때 부르는 메소드
public void updateThread(int parentThread, int previousThread) {
try {
// a. 현존 모든게시물의 thread값을 대상으로 현재 작성 중인 답변글인 부모글의 thread값보다 작고, 이전 새글의 thread값보다 큰 thread를 찾아서 모두 -1 한다.
String sql = "update tblBoards set thread = thread - 1 where thread > ? and thread < ?";
pstat = conn.prepareStatement(sql);
pstat.setInt(1, previousThread);
pstat.setInt(2, parentThread);
pstat.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}
}
- 기존에 BoardDAO에서 사용했던 메소드 수정하기
a. add(BoardDTO dto): sql 구문 수정(thread, depth 컬럼 추가), pstat.setInt 추가하기
b. list(HashMap<String, String> map): dto.setThread, dto.setDepth 추가하기
c. get(String seq): dto.setThread, dto.setDepth 추가하기
// add
public int add(BoardDTO dto) {
try {
// thread, depth 추가!!
String sql = "insert into tblBoards (seq, id, subject, content, regdate, readcount, tag, thread, depth)"
+ " values (seqBoards.nextVal, ?, ?, ?, default, default, ?, ?, ?)";
pstat = conn.prepareStatement(sql);
pstat.setString(1, dto.getId());
pstat.setString(2, dto.getSubject());
pstat.setString(3, dto.getContent());
pstat.setString(4, dto.getTag());
pstat.setInt(5, dto.getThread());
pstat.setInt(6, dto.getDepth());
return pstat.executeUpdate(); // 성공시 1 실패시 0
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
// list
public ArrayList<BoardDTO> list(HashMap<String, String> map) {
try {
String where ="";
if ( map.get("isSearch").equals("y") ) {
if (map.get("column").equals("all")) {
where = String.format(" where subject like '%%%s%%' or content like '%%%s%%' "
, map.get("search"), map.get("search"));
} else {
where = String.format(" where %s like '%%%s%%' "
, map.get("column"), map.get("search"));
}
}
String sql = String.format("select * from (select b.*, rownum as rnum from vwBoard3 b %s) where rnum between %s and %s order by thread desc"
, where
, map.get("begin")
, map.get("end"));
pstat = conn.prepareStatement(sql);
rs = pstat.executeQuery();
ArrayList<BoardDTO> list = new ArrayList<BoardDTO>();
while ( rs.next() ) {
BoardDTO dto = new BoardDTO();
dto.setSeq(rs.getString("seq"));
dto.setName(rs.getString("name"));
dto.setSubject(rs.getString("subject"));
dto.setReadcount(rs.getString("readcount"));
dto.setRegdate(rs.getString("regdate"));
dto.setIsnew(rs.getString("isnew"));
dto.setCcnt(rs.getString("ccnt"));
dto.setThread(rs.getInt("thread"));
dto.setDepth(rs.getInt("depth"));
list.add(dto);
}
return list;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
// get
public BoardDTO get(String seq) {
try {
String sql = "select b.*, (select name from tblUsers where id = b.id ) as name "
+ "from tblBoards b where seq=?";
pstat = conn.prepareStatement(sql);
pstat.setString(1, seq);
rs = pstat.executeQuery();
if ( rs.next() ) {
BoardDTO dto = new BoardDTO();
dto.setSeq(rs.getString("seq"));
dto.setSubject(rs.getString("subject"));
dto.setContent(rs.getString("content"));
dto.setId(rs.getString("id"));
dto.setName(rs.getString("name"));
dto.setReadcount(rs.getString("readcount"));
dto.setRegdate(rs.getString("regdate"));
dto.setTag(rs.getString("tag"));
// view.jsp에 thread와 depth를 넘겨주기위해 추가하기
dto.setThread(rs.getInt("thread"));
dto.setDepth(rs.getInt("depth"));
return dto;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
2) com.test.myapp.board > BoardDTO.java
- 새로운 컬럼 thread, depth 변수 추가하고, getter/setter 만들기
private int thread;
private int depth;
// getter & setter 구현 ..
[ Servlet 작업 - 새글 vs 답변글 구분하기 페이지 ]
1) com.test.myapp.board > AddOk.java
- reply(1 또는 0) 조건문을 이용해서 새 글을 쓰는것인지, 답변 글을 쓰는것인지 구별한다.
- 새글에는 depth (탭기능)을 0으로 시작하게 한다.
- 답변글의 thread는 부모thread의 - 1, depth는 부모depth의 + 1을 한다.
@WebServlet("/board/addok.do")
public class AddOk extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
CheckMember cm = new CheckMember();
cm.check(req, resp);
// 1.
String subject = req.getParameter("subject");
String content = req.getParameter("content");
String tag = req.getParameter("tag");
// 2.
BoardDAO dao = new BoardDAO();
BoardDTO dto = new BoardDTO();
// 로그인한 아이디를 가져오기 위해 session을 가져온다.
HttpSession session = req.getSession();
dto.setId(session.getAttribute("id").toString());
dto.setSubject(subject);
dto.setContent(content);
dto.setTag(tag);
// 새 글쓰기 vs 답변 글쓰기
String reply = req.getParameter("reply"); // 1 or 0
int thread = -1; // 현재글
int depth = -1;
int parentThread = -1; // 부모글
int parentDepth = -1;
if ( reply.equals("0") ) {
// 새 글쓰기
// a. 현존 모든 게시물 중에서 가장 큰 thread값을 찾는다. > 0 > 그 찾은 thread값에 +1000 한 값을 현재 새글의 thread값으로 사용한다.
thread = dao.getMaxThread();
// b. 현재 새글의 depth는 0을 넣는다.
depth = 0;
} else {
// 답변 글쓰기
// addok.jsp에서 넘겨준 부모글의 thread, depth 받기
parentThread = Integer.parseInt(req.getParameter("thread"));
parentDepth = Integer.parseInt(req.getParameter("depth"));
// a. 현존 모든게시물의 thread값을 대상으로 현재 작성 중인 답변글인 부모글의 thread값보다 작고, 이전 새글의 thread값보다 큰 thread를 찾아서 모두 -1 한다.
// 이전 새글의 thread는 얼마인데???
int previousThread = (int)Math.floor( (parentThread - 1) / 1000 ) * 1000; // 공식
dao.updateThread(parentThread, previousThread);
// b. 현재 작성중인 답변글의 thread값을 부모글의 thread - 1을 넣는다.
thread = parentThread - 1;
// c. 현재 작성중인 답변글의 depth값을 부모글의 depth + 1을 넣는다.
depth = parentDepth + 1;
}
dto.setThread(thread);
dto.setDepth(depth);
int result = dao.add(dto);
// 3.
if ( result == 1 ) {
resp.sendRedirect("/myapp/board/list.do");
} else {
resp.sendRedirect("/myapp/board/add.do");
}
}
}
[ JSP 작업 ]
- 답변달기는 해당 글의 상세 페이지에서 할 수 있으므로 추가하기
- 페이지 이동시 BoardDTO 에 저장된 thread, depth값 넘기기
*이전 작성했던 파일 그대로 사용했으며, 어제 구현했던 코드는 일부 제거했습니다.
<div class="btns">
<c:if test="${ not empty id }">
<c:if test="${ dto.id == id }"> <!-- 해당글의 글쓴 아이디와 로그인한 사람이 동일하다면.. -->
<button type="button" class="btn btn-primary"
onclick="location.href='/myapp/board/edit.do?seq=${ dto.seq }';">수정하기</button>
<button type="button" class="btn btn-primary"
onclick="location.href='/myapp/board/del.do?seq=${ dto.seq }';">삭제하기</button>
</c:if>
<button type="button" class="btn btn-primary"
onclick="location.href='/myapp/board/add.do?reply=1&thread=${ dto.thread }&depth=${ dto.depth }';">답변달기</button>
</c:if>
<button type="button" class="btn btn-default"
onclick="location.href='/myapp/board/list.do?column=${ column }&search=${ search }';">돌아가기</button>
</div>
- 게시판 글 답변 쓰기 전 페이지 ( http://localhost:8090/myapp/board/list.do?page=6 )
- 게시판 글 답변 쓰고 난 후 페이지 ( http://localhost:8090/myapp/board/list.do?page=6 )
[ 글 번호, 답변 달린글 삭제 구현시 생각해야할 것 ]
1) 글 번호
case 1. 게시글 번호만 보이게한다. ( 답변글 번호는 안보이게 함 ) --> 현재 구현한 것
case 2. 임의로 숫자를 차례대로(1,2,3,4,5,....) 재 할당한다. ( 글 삭제시 뒤죽박죽 된다. ) --> 의미 X
case 3. 그냥 둔다. ( 게시글 번호(seq), 답변글 번호(seq) 뒤죽박죽 출력 )
2) 답변 달린 글 삭제
case 1. 답변 달린 글은 삭제를 못하게한다.
case 2. 댓글 기능 구현했을 때 처럼 답변글(자식)을 전부 삭제 처리한다. ( 가장 무난함 )
case 3. "해당 글은 삭제 되었습니다"로 변경하여 클릭도 불가능하게 하고, DB에도 사라짐 ( update )
MEMO>
# 게시판구현이 3~4일정도 진행했는데 정리한 내용이 뒤죽박죽 섞여있어서 보기 힘든것 같다. 처음부터 끝까지 혼자 구현을 해봐야겠다.
# 현재 만든 기본 게시판은 자잘자잘한 버그가 많이 있다고 한다. 조금씩 찾아서 따로 구현해보자
# 단계별로 차근차근, 복잡하다고 생각하지말고 흐름을 파악하자
# 답변 달기 기능에서 쓰이는 thread, depth를 이용한 알고리즘이 정확히 명시 되어있다고 한다.
'인천일보아카데미 > - 학습일지' 카테고리의 다른 글
[학습일지]JAVA교육일지 게시판CRUD 7⭐페이징기능 (0) | 2022.08.05 |
---|---|
[학습일지]JAVA교육일지 게시판CRUD 6⭐게시판보안정책 (0) | 2022.07.21 |
[학습일지]JAVA교육일지 게시판CRUD 6⭐ 댓글기능 (0) | 2022.07.20 |
[학습일지]JAVA교육일지 게시판CRUD 5⭐검색기능(2) (0) | 2022.07.20 |
[학습일지]JAVA교육일지 게시판CRUD 4⭐검색기능(1) (0) | 2022.07.20 |