본문 바로가기
인천일보아카데미/- 학습일지

[학습일지]JAVA교육일지 게시판CRUD 8⭐답변기능

by w1z 2022. 11. 10.

기본 게시판 만들기

  - 답변 기능 추가하기


답변 기능 추가하기

[ 구현 목표 ]

- 게시판 리스트 항목에서 글 하나를 선택 후 답변을 달 수 있다.

- 글 > 답변 > 답변 > 답변 > ... 이런식으로 계속 답변을 달 수 있다.

- 게시판 글의 답변 번호는 보이지 않게 한다.

 

[ 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를 이용한 알고리즘이 정확히 명시 되어있다고 한다.