쌍용교육(JAVA)/MVC

쌍용교육 -JSP수업 63~64일차 - ch06_mvcPageMVC(9)

구 승 2024. 5. 21. 09:06

GetFavAction

package kr.board.action;

import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.codehaus.jackson.map.ObjectMapper;

import kr.board.dao.BoardDAO;
import kr.board.vo.BoardFavVO;
import kr.controller.Action;

public class GetFavAction implements Action {

	@Override
	public String execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		request.getParameter("utf-8");
		
		int board_num = Integer.parseInt(request.getParameter("board_num"));
		
		Map<String,Object> mapAjax = new HashMap<String,Object>();
		
		HttpSession session = request.getSession();
		Integer user_num = (Integer)session.getAttribute("user_num");
		BoardDAO dao = BoardDAO.getInstance();
		if(user_num==null) { //로그인이 되지 않은 경우
			mapAjax.put("status","noFav"); //noFav는 클릭을 하지 않은 것
		}else {//로그인이 된 경우
			BoardFavVO boardFav = dao.selectFav(new BoardFavVO(board_num,user_num));
			
			if(boardFav !=null) {
				//로그인한 회원이 좋아요를 클릭
				mapAjax.put("status", "yesFav");
			}else {
				//로그인한 회원이 좋아요를 미클릭
				mapAjax.put("status", "noFav");
			}
		}
		//좋아요 개수
		mapAjax.put("count", dao.selectFavCount(board_num));
		
		//JSON 데이터 생성
		ObjectMapper mapper = new ObjectMapper();
		String ajaxData = mapper.writeValueAsString(mapAjax);
		
		request.setAttribute("ajaxData",ajaxData);
		
		return "/Web-INF/views/common/ajax_view.jsp";
	}

}

detail.jsp 추가 명시

Js파일 추가

<script type="text/javascript" src="${pageContext.request.contextPath}/js/board.fav.js"></script>

이미지 추가

<%-- 좋아요 --%>

<img id ="output_fav" data-num="${board.board_num}"

src="${pageContext.request.contextPath}/images/fav01.gif" width="50">

좋아요

<span id="output_fcount"></span>

위에  파일 만들고 내용명시 ( board.fav.js")

$(function(){
	/*=========================
	 * 좋아요 선택 여부와 선택한 총 개수 읽기
	 *==========================*/
	 function selectFav(){
		 //서버와 통신
		 $.ajax({
			 url:'getFav.do',
			 type:'post',
			 data:{board_num:$('#output_fav').attr('data-num')},
			 dataType:'json',
			 success:function(param){
				 displayFav(param);
			 },
			 error:function(){
				 alert('네트워크 오류 발생');
			 }
		 });
	 }
	 /*=========================
	 * 좋아요 표시 함수
	 *==========================*/
	function displayFav(param){
		let output;
		if(param.status == 'yesFav'){//좋아요 선택
			output = '../images/fav02/gif';
		}else{//좋아요 미선택
			output = '../images/fav01.gif';
		}
		//문서 객체에 설정
		$('#output_fav').attr('src',output);
		$('#output_fcount').text(param.count);
	}
	 /*=========================
	 * 초기 데이터 호출
	 *==========================*/
	selectFav();
});

BoardDAO 추가 명시(좋아요 등록) => 좋아요 갯수 위에 명시
                                  (좋아요 삭제) => 좋아요 갯수 아래에 명시

//좋아요 등록 
		public void insertFav(BoardFavVO favVO) throws Exception{
			Connection conn =null;
			PreparedStatement pstmt = null;
			String sql =null;
			
			try {
				//커넥션 풀로부터 커넥션 할당
				conn=DBUtil.getConnection();
				sql="INSERT INTO zboard_fav (board_num,mem_num) VALUES (?,?)";
				//preparedStatement 객체 생성
				pstmt=conn.prepareStatement(sql);
				//?에 데이터 바인딩
				pstmt.setInt(1, favVO.getBoard_num());
				pstmt.setInt(2, favVO.getMem_num());
				//SQL문 실행
				pstmt.executeUpdate();
			}catch(Exception e) {
				//예외 발생
				conn.rollback();
				throw new Exception(e);
			}finally {
				DBUtil.executeClose(null, pstmt, conn);
			}
		}
		//좋아요 개수
		public int selectFavCount(int board_num)throws Exception{
			Connection conn = null;
			PreparedStatement pstmt = null;
			ResultSet rs = null;
			String sql =null;
			int count = 0;
			try {
				conn = DBUtil.getConnection();
				
				sql = "SELECT COUNT(*) FROM zboard_fav WHERE board_num=?";
				
				pstmt = conn.prepareStatement(sql);
				pstmt.setInt(1, board_num);
				
				rs = pstmt.executeQuery();
				if(rs.next()) {
					count = rs.getInt(1);
				}
			}catch(Exception e) {
				throw new Exception(e);
			}finally {
				DBUtil.executeClose(rs, pstmt, conn);
			}
			return count;
		}
		//회원번호와 게시물 번호를 이용한 좋아요 정보
		//(회원이 게시물을 호출했을 때 좋아요 선택 여부 표시)
		public BoardFavVO selectFav(BoardFavVO favVO)throws Exception{
			Connection conn = null;
			PreparedStatement pstmt = null;
			ResultSet rs = null;
			BoardFavVO fav = null;
			String sql = null;
			try {
				conn = DBUtil.getConnection();
				
				sql = "SELECT * FROM zboard_fav WHERE board_num=? AND mem_num=?";
				
				pstmt = conn.prepareStatement(sql);
				pstmt.setInt(1, favVO.getBoard_num());
				pstmt.setInt(2, favVO.getMem_num());
				
				rs = pstmt.executeQuery();
				if(rs.next()) {
					fav = new BoardFavVO();
					fav.setBoard_num(rs.getInt("board_num"));
					fav.setMem_num(rs.getInt("mem_num"));
				}
			}catch(Exception e) {
				throw new Exception(e);
			}finally{
				DBUtil.executeClose(rs, pstmt, conn);
			}
			return fav;
		}
		//좋아요 삭제
		public void deleteFav(BoardFavVO favVO) throws Exception{
			Connection conn = null;
			PreparedStatement pstmt = null;
			String sql =null;
			try {
				conn = DBUtil.getConnection();
				sql="DELETE FROM zboard_fav WHERE board_num=? AND mem_num=?";
				//preparedStatement 객체 생성
				pstmt=conn.prepareStatement(sql);
				//?에 데이터 바인딩
				pstmt.setInt(1, favVO.getBoard_num());
				pstmt.setInt(2, favVO.getMem_num());
				//SQL문 실행
				pstmt.executeUpdate();
				
			}catch(Exception e) {
				throw new Exception(e);
			}finally{
				DBUtil.executeClose(null, pstmt, conn);
			}
		}

WriteFavAction

package kr.board.action;

import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.codehaus.jackson.map.ObjectMapper;

import kr.board.dao.BoardDAO;
import kr.board.vo.BoardFavVO;
import kr.controller.Action;

public class WriteFavAction implements Action {

	@Override
	public String execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		Map<String,Object> mapAjax =
				new HashMap<String,Object>();
		
		HttpSession session = request.getSession();
		Integer user_num = (Integer)session.getAttribute("user_num");
		if(user_num==null) {//로그인이 되지 않은 경우
			mapAjax.put("result", "logout");
		}else {//로그인 된 경우
			//전송된 데이터 인코딩 타입 지정
			request.setCharacterEncoding("utf-8");
			//전송된 데이터 반환
			int board_num = Integer.parseInt(request.getParameter("board_num"));
			
			BoardFavVO favVO = new BoardFavVO();
			favVO.setBoard_num(board_num);
			favVO.setMem_num(user_num);
			
			BoardDAO dao = BoardDAO.getInstance();
			
			//좋아요 등록 여부 체크
			BoardFavVO db_fav = dao.selectFav(favVO);
			if(db_fav!=null) {//좋아요 등록 O
				//좋아요 삭제
				dao.deleteFav(db_fav);
				mapAjax.put("status", "noFav");
			}else {//좋아요 등록 X
				//좋아요 등록
				dao.insertFav(favVO);
				mapAjax.put("status", "yesFav");
			}
			mapAjax.put("result", "success");
			mapAjax.put("count", dao.selectFavCount(board_num));
		}
		//JSON 데이터 생성
		ObjectMapper mapper = new ObjectMapper();
		String ajaxData = mapper.writeValueAsString(mapAjax);
		
		request.setAttribute("ajaxData", ajaxData);
		return "/WEB-INF/views/common/ajax_view.jsp";
	}

}

board.fav.js 내용추가

좋아요 등록 (및 삭제) 이벤트 연결

/*=========================
	 * 좋아요 등록 (및 삭제) 이벤트 연결
	 *==========================*/
	$('#output_fav').onclick(function(){
		//서버와 통신
		$.ajax({
			url:'writeFav.do',
			type:'post',
			data:{board_num:$(this).attr('data-num')},
			dataType:'json',
			success:function(param){
				if(param.result == 'logout'){
					alert('로그인 후 좋아요를 눌러주세요');
				}else if(param.result == 'success'){
					displayFav(param);
				}else{
					alert('좋아요 등록/삭제 오류 발생');
				}
			},
			error:function(){
				alert('네트워크 오류 발생');
			}
			
		});
	});

클릭하기전
좋아요 클릭 후 (로그인시만 가능)

BoardDAO 추가 명시 ( 내가 선택한 좋아요 목록)  => 좋아요 삭제 밑

b.mem_num 은 작성자. f.mem_num은 좋아요를 누른사람

이걸 이제 DAO에 명시하기.

//내가 선택한 좋아요 목록
		public List<BoardVO> getListBoardFav(int start, int end , int mem_num) throws Exception{
			Connection conn = null;
			PreparedStatement pstmt = null;
			ResultSet rs = null;
			List<BoardVO> list = null;
			String sql =null;
			try {
				//커넥션풀로부터 커넥션 할당
				conn = DBUtil.getConnection();
				//SQL문 작성
				//(주의) zboard_fav의 회원번호(좋아요 클릭한 회원번호)로 검색되어야 하기 때문에 f.mem_num으로 표기해야 함
				sql="SELECT * FROM (SELECT a.*, rownum rnum FROM "
						+ "(SELECT * FROM zboard b JOIN "
						+ "zmember m USING(mem_num) JOIN zboard_fav f "
						+ "USING(board_num) WHERE f.mem_num=? ORDER BY "
						+ "board_num DESC)a) WHERE rnum >=? AND rnum <=?";
				//PreparedStatement 객체 생성
				pstmt = conn.prepareStatement(sql);
				//?에 데이터 바인딩
				pstmt.setInt(1,mem_num);
				pstmt.setInt(2, start);
				pstmt.setInt(3, end);
				//SQL문 실행
				rs=pstmt.executeQuery();
				list = new ArrayList<BoardVO>();
				while(rs.next()) {
					BoardVO board = new BoardVO();
					board.setBoard_num(rs.getInt("board_num"));
					board.setTitle(StringUtil.useBrNoHTML(rs.getString("title")));
					board.setReg_date(rs.getDate("reg_date"));
					board.setId(rs.getString("id")); //조인을 했기 떄문에 id를 가져오기가 가능하다.
					
					list.add(board);
				}
			}catch(Exception e) {
				throw new Exception(e);
			}finally{
				DBUtil.executeClose(rs, pstmt, conn);
			}
			return list;
		}

kr.member.action => MypageAction에 있는 파일 수정

package kr.member.action;

import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import kr.board.dao.BoardDAO;
import kr.board.vo.BoardVO;
import kr.controller.Action;
import kr.member.dao.MemberDAO;
import kr.member.vo.MemberVO;
  
public class MyPageAction implements Action{

	@Override
	public String execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpSession session = request.getSession();
		Integer user_num = (Integer)session.getAttribute("user_num");
		if(user_num == null) {//로그인이 되지 않은 경우
			return "redirect:/member/loginForm.do";
		}
		//로그인이 된 경우
		//회원정보
		MemberDAO dao = MemberDAO.getInstance();
		MemberVO member = dao.getMember(user_num);
		
		//관심 게시물 정보
		BoardDAO boardDAO = BoardDAO.getInstance();
		List<BoardVO> boardList = boardDAO.getListBoardFav(1, 5, user_num);
		
		request.setAttribute("member", member);
		request.setAttribute("boardList", boardList);
		//JSP 경로 반환
		return "/WEB-INF/views/member/myPage.jsp";
	}

}

추가내용

//관심 게시물 정보

BoardDAO boardDAO = BoardDAO.getInstance();

List<BoardVO> boardList = boardDAO.getListBoardFav(1, 5, user_num);

추가한 내용을 저장하기

request.setAttribute("boardList", boardList);

 

member/myPage.jsp 수정

관심게시물 목록 h3 태그부터 수정 시작 (141라인)

<h3>관심 게시물 목록</h3>
			<table>
				<tr>
					<th>제목</th>
					<th>작성자</th>
					<th>등록일</th>
				</tr>
				<c:forEach var="board" items="${boardList}">
				<tr>
					<td><a href="${pageContext.request.contextPath}/board/detail.do?board_num=${board.board_num}" target="_blank">${fn:substring(board.title,0,12)}</a></td>
					<td>${board.id}</td>
					<td>${board.reg_date}</td>
				</tr>
				</c:forEach>
			</table>

상단에 추가 명시
<%@
taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>

table.sql 추가 명시

--댓글
create table zboard_reply(
re_num number not null,
re_content varchar2(900) not null,
re_date date default sysdate not null,
re_modifydate date,
re_ip varchar2(40) not null,
board_num number not null,
mem_num number not null,
constraint zreply_pk primary key (re_num),
constraint zreply_fk1 foreign key (board_num) references zboard (board_num),
constraint zreply_fk2 foreign key (mem_num) references zmember (mem_num)
);
create sequence zreply_seq;

kr.board.vo=>BoardReplyVO

package kr.board.vo;

public class BoardReplyVO {
	private int re_num; 			//댓글번호
	private String re_content; 		//내용
	private String re_date;			//등록일 (1초전 1분전 이러한 방식으로 사용하고자 String 으로 사용)
	private String re_modifydate;	//수정일
	private String re_ip;			//아이피주소
	private int board_num; 			//부모글번호
	private int mem_num;			//작성자 회원번호
	
	private String id;				//작성자 id (조인해서 가져오려고 만듬)

	public int getRe_num() {
		return re_num;
	}

	public void setRe_num(int re_num) {
		this.re_num = re_num;
	}

	public String getRe_content() {
		return re_content;
	}

	public void setRe_content(String re_content) {
		this.re_content = re_content;
	}

	public String getRe_date() {
		return re_date;
	}

	public void setRe_date(String re_date) {
		this.re_date = re_date;
	}

	public String getRe_modifydate() {
		return re_modifydate;
	}

	public void setRe_modifydate(String re_modifydate) {
		this.re_modifydate = re_modifydate;
	}

	public String getRe_ip() {
		return re_ip;
	}

	public void setRe_ip(String re_ip) {
		this.re_ip = re_ip;
	}

	public int getBoard_num() {
		return board_num;
	}

	public void setBoard_num(int board_num) {
		this.board_num = board_num;
	}

	public int getMem_num() {
		return mem_num;
	}

	public void setMem_num(int mem_num) {
		this.mem_num = mem_num;
	}

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}
	
}

views/board/detail.jsp (UI추가) => 맨아래에 명시 73라인 다음

<!-- 댓글 시작 -->
		<div id = "reply_div">
			<span class="re-title">댓글 달기</span>
			<form id="re_form">
				<input type="hidden" name="board_num" value="${board.board_num}" id="board_num">
				<!-- disabled는 댓글을 달지 못하도록 비활설화 시키는 역할 -->
				<textarea rows="3" cols="50" name="re_content" 
				<c:if test="${empty user_num}">disabled="disabled"</c:if> 
				id="re_content" class="rep-content"><c:if test="${empty user_num}">로그인해야 작성할 수 있습니다</c:if>
				</textarea>
				<c:if test="${!empty user_num}">
				<div id="re_first">
					<span class="letter-count">300/300</span><!-- 글자수 제한  -->
				</div>
				<div id="re_second" class="align-right">
					<input type="submit" value="전송">
				</div>
				</c:if>
			</form>
		</div>
			<!-- 댓글 목록 출력 시작 -->
			<div id="output"></div>
			<div class="paging-button" style="display:none;">
				<input type="button" value="다음글 보기">
			</div>
			<div id="loading" style="display:none">
				<img src="${pageContext.request.contextPath}/images/loading.gif" width="50" height="50">
			</div>
			<!-- 댓글 목록 출력 끝 -->
		</div>
		<!-- 댓글 끝 -->

 

style.css 추가

/* 게시판 글상세
---------------------*/
ul.detail-info li{
	display:inline-block;
}
.detail-img{
	max-width:500px;
}
ul.detail-sub{
	margin:0;
	padding:0;
}
ul.detail-sub li{
	display:inline-block;
	width:49%;
	height:50px;
	vertical-align:middle;
}
ul.detail-sub li:first-child img{
	vertical-align:middle;
}
ul.detail-sub li:last-child{
	text-align:right;
	line-height:250%;
}
#output_fav{
	cursor:pointer;
}
div#reply_div{
	padding:5px 10px 40px 10px;
	margin-top:10px;
	background-color:#EEEEEE;
}
form #re_form{
	width:650px;
	border:none;
}
span.re-title{
	color:#000;
	font-size:12pt;
	line-height:200%;
}
span.letter-count{
	font-size:10pt;
	color:#999;
}
textarea.rep-content{
	width:90%;
	height:50px;
	margin:10px;
}
div#re_first, div#mre_first{
	float:left;
	width:70%;
	padding-left:15px;
	margin-bottom:10px;
	
}
div#re_second, div#mre_second{
	float:left;
	width:19%;
	margin-bottom:10px;
}
div#loading{
	text-align:center;
}
div.paging-button{
	text-align:right;
}
div#output{
	clear:both;
}
form#mre_form{
	border:none;
	margin:5px;
}

BoardDAO 추가 명시(64일차 시작) 

//댓글 등록
		public void insertReplyBoard(BoardReplyVO boardReply) throws Exception{
			Connection conn = null;
			PreparedStatement pstmt = null;
			String sql =null;
			try {
				//커넥션풀로부터 커넥션 할당
				conn = DBUtil.getConnection();
				sql = "INSERT INTO zboard_reply (re_num,re_content,"
						+ "re_ip,mem_num,board_num) VALUES (zreply_seq.nextval,?,?,?,?)";
				pstmt =conn.prepareStatement(sql);
				pstmt.setString(1,boardReply.getRe_content());
				pstmt.setString(2,boardReply.getRe_ip());
				pstmt.setInt(3,boardReply.getMem_num());
				pstmt.setInt(4,boardReply.getBoard_num());
				
				pstmt.executeUpdate();
			}catch(Exception e) {
				throw new Exception(e);
			}finally {
				DBUtil.executeClose(null, pstmt, conn);
			}
		}

WriteReplyAction 

package kr.board.action;

import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.codehaus.jackson.map.ObjectMapper;

import kr.board.dao.BoardDAO;
import kr.board.vo.BoardReplyVO;
import kr.controller.Action;

public class WriteReplyAction implements Action{

	@Override
	public String execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		
		//Map은 SON 응답으로 보낼 데이터를 포함한다. 그러므로 아래에 ObjectMapper 를 사용해야됨
		Map<String,String> mapAjax = 
				new HashMap<String,String>();
		
		HttpSession session = request.getSession();
		Integer user_num = (Integer)session.getAttribute("user_num");
		if(user_num == null) { //로그인이 안된경우
			mapAjax.put("result", "logout");
		}else {
			//전송된 데이터 인코딩 타입 지정
			request.setCharacterEncoding("utf-8");
			//자바빈을(VO)를 생성해서 전송된 데이터 저장
			BoardReplyVO reply = new BoardReplyVO();
			reply.setMem_num(user_num);//댓글 작성자 회원번호
			reply.setRe_content(request.getParameter("re_content"));
			reply.setRe_ip(request.getRemoteAddr());
			reply.setBoard_num(Integer.parseInt(request.getParameter("board_num"))); //댓글의 부모 글번호
			
			//위에 받은 4개의 데이터를 전달
			BoardDAO dao = BoardDAO.getInstance();
			dao.insertReplyBoard(reply);
			//전달이 성공했다는 것을 result에 저장해서 Ajax로 보낸다.
			mapAjax.put("result", "success");
		}
		//JSON 데이터로 변환 
		ObjectMapper mapper = new ObjectMapper();
		String ajaxData = mapper.writeValueAsString(mapAjax); //문자열이 만들어짐
		
		//만든 문자열을 저장
		request.setAttribute("ajaxData", ajaxData);
		return "/WEB-INF/views/common/ajax_view.jsp";
	}

}

views/board/detail.jsp 수정 

 

<script type="text/javascript" src="${pageContext.request.contextPath}/js/board.reply.js"></script>

스크립트 추가 후 파일 생성


js/board.reply.js

$(function(){
	let rowCount = 10;
	let currentPage;
	let count;
   /*====================================
	*댓글 목록
	* ====================================*/
	function selectList(pageNum){
		
	}
	/*====================================
	*댓글 등록
	* ====================================*/
	//댓글 등록
	$('#re_form').submit(function(event){
		if($('#re_content').val().trim()==''){
			alert('내용을 입력하세요');
			$('#re_content').val('').focus();
			return false;
		}
		
		//form 이하의 태그에 입혁한 데이터를 모두 읽어서 쿼리 스트링으로 반환
		let form_data = $(this).serialize(); //serialize: jQuery 메서드로 폼 데이터를 URL 인코딩된 문자열로 직렬화
		
		//서버와 통신
		$.ajax({
			url:'writeReply.do',
			type:'post',
			data:form_data,
			dataType:'json',
			success:function(param){
				if(param.result == 'logout'){
					alert('로그인을 해야 작성할 수 있습니다.');
				}else if(param.result == 'success'){
					//로그인 성공시 폼 초기화를 해야됨.
					initForm(); //아래에 만들어둔 initForm을 호출 (초기화 하는 역할)
					//댓글 작성이 성공하면 새로 삽입한 글을 포함해서 첫번째 페이지에 게시글 목록을 다시 호출함.
					selectList(1);
				}else{
					alert('댓글 등록 오류');
				}
			},
			error:function(){
				alert('네트워크 오류 발생');
			}
		});
		
		
		
		//기본 이벤트 제거
		event.preventDefault();
	});
	//댓글 작성 폼 초기화
	function initForm(){
		$('textarea').val('');
		$('#re_first .letter-count').text('300/300'); //#re_first .letter-count 사이에 공백이 있는 경우는 후손선택자로 들어가기 때문에
	}
	
	/*====================================
	*댓글 수정
	* ====================================*/
	
	/*====================================
	*댓글 등록 및 수정 공통
	* ====================================*/
	//textarea에 내용 입력시 글자 수 체크
	$(document).on('keyup','textarea',function(){
		//입력한 글자수 구함
		let inputLength = $(this).val().length;
		
		if(inputLength > 300){ //300자를 넘어선 경우
			$(this).val($(this).val().substring(0,300)); //substring: JavaScript 문자열 메서드로, 문자열의 일부를 추출. 두 인덱스를 사용하여 시작 위치와 끝 위치를 지정
			
		}else{//300자 이하인 경우
			let remain = 300 - inputLength;
			remain +='/300';
			if($(this).attr('id') == 're_content'){
				//등록폼 글자수
				$('#re_first .letter-count').text(remain);
			}else{
				//수정폼 글자수
				$('#mre_first .letter-count').text(remain);
			}
		}
	});
	/*====================================
	*댓글 삭제
	* ====================================*/
	
	/*====================================
	*초기 데이터(목록) 호출
	* ====================================*/
	selectList(1); //댓글 목록의 function이름
});

이후 댓글을 입력하면 sqldeveloper에 값이 들어간다. (아직 HTML에는 안올라옴)

BoardDAO 추가 명시 

//댓글 개수
		public int getReplyBoardCount(int board_num)throws Exception{
			Connection conn = null;
			PreparedStatement pstmt = null;
			String sql =null;
			ResultSet rs = null;
			int count = 0;
			
			try {
				//커넥션풀로부터 커넥션 할당
				conn = DBUtil.getConnection();
				//SQL문 작성
				sql="SELECT COUNT(*) FROM zboard_reply WHERE board_num=?";
				pstmt =conn.prepareStatement(sql);
				pstmt.setInt(1, board_num);
				//SQL문 실행
				rs = pstmt.executeQuery();
				if(rs.next()) {
					count = rs.getInt(1); //첫 번째 열의 값을 가져온다.
				}
			}catch(Exception e) {
				throw new Exception(e);
			}finally {
				DBUtil.executeClose(rs, pstmt, conn);
			}
			
			return count;
		}
		//댓글 목록
		public List<BoardReplyVO> getListReplyBoard(int start, int end, int board_num) throws Exception{
			Connection conn = null;
			PreparedStatement pstmt = null;
			String sql =null;
			ResultSet rs = null;
			List<BoardReplyVO> list = null;
			try {
				//커넥션풀로부터 커넥션 할당
				conn = DBUtil.getConnection();
				//SQL문 작성
				sql="SELECT * FROM (SELECT a.* rownum rnum FROM "
						+ "(SELECT * FROM zboard_reply JOIN zmember USING(mem_num) "
						+ "WHERE board_num=? ORDER BY re_num DESC)a) "
						+ "WHERE rnum >=? AND rnum <=?";
				//PreparedStatment 객체 생성
				pstmt = conn.prepareStatement(sql);
				//?에 데이터 바인딩
				pstmt.setInt(1, board_num);
				pstmt.setInt(2, start);
				pstmt.setInt(3, end);
				//SQL문 실행
				rs = pstmt.executeQuery();
				list = new ArrayList<BoardReplyVO>();
				while(rs.next()) {
					BoardReplyVO reply = new BoardReplyVO();
					reply.setRe_num(rs.getInt("re_num"));
					reply.setRe_date(rs.getString("re_date"));
					reply.setRe_modifydate(rs.getString("re_modifydate"));
					reply.setRe_content(StringUtil.useBrNoHTML(rs.getString("re_content")));
					reply.setBoard_num(rs.getInt("board_num"));
					reply.setMem_num(rs.getInt("mem_num"));//작성자 회원번호
					reply.setId(rs.getString("id"));//작성자 아이디 (조인했기 때문에 가져올 수 있음)
					
					list.add(reply);
				}
			}catch(Exception e) {
				throw new Exception(e);
			}finally {
				DBUtil.executeClose(rs, pstmt, conn);
			}
			return list;
		}

ListReplyAction

package kr.board.action;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.codehaus.jackson.map.ObjectMapper;

import kr.board.dao.BoardDAO;
import kr.board.vo.BoardReplyVO;
import kr.controller.Action;
import kr.util.PagingUtil;

public class ListReplyAction implements Action{

	@Override
	public String execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		//전송된 데이터 인코딩 타입 지정
		request.setCharacterEncoding("utf-8"); //숫자만 날라오긴 하지만 명시하는게 좋음
		
		String pageNum = request.getParameter("pageNum");
		if(pageNum == null) {
			pageNum = "1";
		}
		String rowCount = request.getParameter("rowCount");
		if(rowCount == null) {
			rowCount = "10";
		}
		
		int board_num = Integer.parseInt(request.getParameter("board_num"));
		
		BoardDAO dao = BoardDAO.getInstance();
		int count = dao.getReplyBoardCount(board_num);
		
		/*
		 * ajax 방식으로 목록을 표시하기 때문에 PagingUtil은 페이지수 표시가
		 * 목적이 아니라 목록 데이터의 페이지 처리를 위해 rownum 번호를 구하는 것이 목적
		 */
		PagingUtil page = new PagingUtil(Integer.parseInt(pageNum),count,Integer.parseInt(rowCount));
		List<BoardReplyVO> list = null;
		if(count > 0) {
			list = dao.getListReplyBoard(page.getStartRow(),page.getEndRow(), board_num);
		}else {
			list = Collections.emptyList(); //비어있는 리스트를 만들어서 반환 (Collections가 기존에 있는 잭슨어쩌구 라고한다)
		}
		
		HttpSession session = request.getSession();
		Integer user_num = (Integer)session.getAttribute("user_num");
		
		Map<String,Object> mapAjax = new HashMap<String,Object>();
		mapAjax.put("count", count);
		mapAjax.put("list", list);
		//로그인한 사람이 작성자인지 체크하기 위해서 로그인한 회원번호 전송
		mapAjax.put("user_num", user_num);
		
		//JSON 문자열로 반환
		ObjectMapper mapper = new ObjectMapper();
		String ajaxData = mapper.writeValueAsString(mapAjax);
		
		request.setAttribute("ajaxData", ajaxData);
		
		return "/WEB-INF/views/common/ajax_view.jsp";
	}

}

PagingUtil.java(다시명시)

rownum번호를 알아내기 위함인데 코드가 부족해서 다시 업로드(44번라인)

package kr.util;

public class PagingUtil {
	private int startRow;	 // 한 페이지에서 보여줄 게시글의 시작 번호
	private int endRow;	 // 한 페이지에서 보여줄 게시글의 끝 번호
	private StringBuffer page;// 페이지 표시 문자열

	/**
	 * currentPage : 현재페이지
	 * count : 전체 게시물 수
	 * rowCount : 한 페이지의  게시물의 수
	 * pageCount : 한 화면에 보여줄 페이지 수
	 * pageUrl : 호출 페이지 url
	 * addKey : 부가적인 key 없을 때는 null 처리 (&num=23형식으로 전달할 것)
	 * */
	public PagingUtil(int currentPage,int count, int rowCount) {
		//ajax 작업을 할 때 페이지 번호가 보여지는 것이 아니라 다음글 보기 버튼을 누르면 다음 페이지가 보여지는 형식의 작업을
		//할 때 목록 데이터를 호출하기 위해 사용(startRow,endRow 를 구하기 위한 용도로만 사용)
		this(null,null,currentPage,count,rowCount,0,null,null);
	}
	public PagingUtil(int currentPage, int count, int rowCount,
			int pageCount, String pageUrl) {
		this(null,null,currentPage,count,rowCount,pageCount,pageUrl,null);
	}
	public PagingUtil(int currentPage, int count, int rowCount,
			int pageCount, String pageUrl, String addKey) {
		this(null,null,currentPage,count,rowCount,pageCount,pageUrl,addKey);
	}
	public PagingUtil(String keyfield, String keyword, int currentPage, int count, int rowCount,
			int pageCount,String pageUrl) {
		this(keyfield,keyword,currentPage,count,rowCount,pageCount,pageUrl,null);
	}
	public PagingUtil(String keyfield, String keyword, int currentPage, int count, int rowCount,
			int pageCount,String pageUrl,String addKey) {

		if(count >= 0) {
			String sub_url = "";
			if(keyword != null) sub_url = "&keyfield="+keyfield+"&keyword="+keyword;
			if(addKey != null) sub_url += addKey;

			// 전체 페이지 수
			int totalPage = (int) Math.ceil((double) count / rowCount);
			if (totalPage == 0) {
				totalPage = 1;
			}
			// 현재 페이지가 전체 페이지 수보다 크면 전체 페이지 수로 설정
			if (currentPage > totalPage) {
				currentPage = totalPage;
			}
			// 현재 페이지의 처음과 마지막 글의 번호 가져오기.
			startRow = (currentPage - 1) * rowCount + 1;
			endRow = currentPage * rowCount;
			
			// 이전 block 페이지
			page = new StringBuffer();
			if(pageCount > 0) {
				// 시작 페이지와 마지막 페이지 값 구하기.
				int startPage = (int) ((currentPage - 1) / pageCount) * pageCount + 1;
				int endPage = startPage + pageCount - 1;
				// 마지막 페이지가 전체 페이지 수보다 크면 전체 페이지 수로 설정
				if (endPage > totalPage) {
					endPage = totalPage;
				}
				
				if (currentPage > pageCount) {
					page.append("<a href="+pageUrl+"?pageNum="+ (startPage - 1) + sub_url +">");
					page.append("[이전]");
					page.append("</a>");
				}
				//페이지 번호.현재 페이지는 빨간색으로 강조하고 링크를 제거.
				for (int i = startPage; i <= endPage; i++) {
					if (i > totalPage) {
						break;
					}
					if (i == currentPage) {
						page.append("&nbsp;<b><span style='color:red;'>");
						page.append(i);
						page.append("</span></b>");
					} else {
						page.append("&nbsp;<a href='"+pageUrl+"?pageNum=");
						page.append(i);
						page.append(sub_url+"'>");
						page.append(i);
						page.append("</a>");
					}
					page.append("&nbsp;");
				}
				// 다음 block 페이지
				if (totalPage - startPage >= pageCount) {
					page.append("<a href="+pageUrl+"?pageNum="+ (endPage + 1) + sub_url +">");
					page.append("[다음]");
					page.append("</a>");
				}
			}else {
				page.append("<b>[warning]</b>pageCount는 1이상 지정해야 페이지수가 표시됩니다.");
			}
		}
	}
	public StringBuffer getPage() {
		return page;
	}
	public int getStartRow() {
		return startRow;
	}
	public int getEndRow() {
		return endRow;
	}
}

 

 

js/board.reply.js(추가)

$(function(){
	let rowCount = 10;
	let currentPage;
	let count;
   /*====================================
	*댓글 목록
	* ====================================*/
	//댓글 목록
	function selectList(pageNum){
		currentPage = pageNum;
		//로딩 이미지 노출
		$('#loading').show();
		//서버와 통신
		$.ajax({
			url:'listReply.do',
			type:'post',
			//	  식별자   :숫자로바뀐변수
			data:{pageNum:pageNum,rowCount:rowCount,board_num:$('#board_num').val()},
			dataType:'json',
			success:function(param){
				//로딩 이미지 감추기
				$('#loading').hide(); // jQuery를 사용하여 HTML 요소를 숨기는 역할
				count = param.count;
				
				if(pageNum==1){
					//처음 호출시는 해당 ID의 div의 내부 내용물을 제거
					$('#output').empty();
				}
				
				$(param.list).each(function(index,item){ //item은 댓글의 하나의 레코드라고 보면된다.
					let output = '<div class="item">';
					output +='<h4>' + item.id + '</h4>';
					output += '<div class="sub-item">';
					output += '<p>' + item.re_content + '</p>';
					
					if(item.re_modifydate){
						output += '<span class="modify-date">최근 수정일 : ' + item.re_modifydate + '</span>';
					}else{
						output += '<span class="modify-date">최근 등록일 : ' + item.re_date + '</span>';
					}
					//로그인한 회원번호와 작성자의 회원번호 일치 여부 체크
					if(param.user_num == item.mem_num){
						output +=' <input type="button" data-renum="'+ item.re_num +'" value="수정" class="modify-btn">';
						output +=' <input type="button" data-renum="'+ item.re_num +'" value="삭제" class="delete-btn">';
					}
					output +='<hr size="1" noshade width="100%">'
					output += '</div>';
					output += '</div>';
					
					//문서 객체에 추가
					$('#output').append(output);
				});
				
				//page button 처리
				//currentPage: 현재 페이지를 나타내는 변수
				//count: 전체 댓글의 개수
				//rowCount: 한 페이지에 보여질 댓글의 개수
				if(currentPage>=Math.ceil(count/rowCount)){ //Math.ceil() 함수는 주어진 숫자를 올림하여 반환
					//다음페이지가 없음
					$('.paging-button').hide();
				}else{
					//다음페이지가 존재
					$('.paging-button').show();
				}
				
			},
			error:function(){
				$('#loading').hide();
				alert('네트워크 오류 발생');
			}
			
		});
	}
	/*====================================
	*댓글 등록
	* ====================================*/
	//댓글 등록
	$('#re_form').submit(function(event){
		if($('#re_content').val().trim()==''){
			alert('내용을 입력하세요');
			$('#re_content').val('').focus();
			return false;
		}
		
		//form 이하의 태그에 입혁한 데이터를 모두 읽어서 쿼리 스트링으로 반환
		let form_data = $(this).serialize(); //serialize: jQuery 메서드로 폼 데이터를 URL 인코딩된 문자열로 직렬화
		
		//서버와 통신
		$.ajax({
			url:'writeReply.do',
			type:'post',
			data:form_data,
			dataType:'json',
			success:function(param){
				if(param.result == 'logout'){
					alert('로그인을 해야 작성할 수 있습니다.');
				}else if(param.result == 'success'){
					//로그인 성공시 폼 초기화를 해야됨.
					initForm(); //아래에 만들어둔 initForm을 호출 (초기화 하는 역할)
					//댓글 작성이 성공하면 새로 삽입한 글을 포함해서 첫번째 페이지에 게시글 목록을 다시 호출함.
					selectList(1);
				}else{
					alert('댓글 등록 오류');
				}
			},
			error:function(){
				alert('네트워크 오류 발생');
			}
		});
		
		
		
		//기본 이벤트 제거
		event.preventDefault();
	});
	//댓글 작성 폼 초기화
	function initForm(){
		$('textarea').val('');
		$('#re_first .letter-count').text('300/300'); //#re_first .letter-count 사이에 공백이 있는 경우는 후손선택자로 들어가기 때문에
	}
	
	/*====================================
	*댓글 수정
	* ====================================*/
	
	/*====================================
	*댓글 등록 및 수정 공통
	* ====================================*/
	//textarea에 내용 입력시 글자 수 체크
	$(document).on('keyup','textarea',function(){
		//입력한 글자수 구함
		let inputLength = $(this).val().length;
		
		if(inputLength > 300){ //300자를 넘어선 경우
			$(this).val($(this).val().substring(0,300)); //substring: JavaScript 문자열 메서드로, 문자열의 일부를 추출. 두 인덱스를 사용하여 시작 위치와 끝 위치를 지정
			
		}else{//300자 이하인 경우
			let remain = 300 - inputLength;
			remain +='/300';
			if($(this).attr('id') == 're_content'){
				//등록폼 글자수
				$('#re_first .letter-count').text(remain);
			}else{
				//수정폼 글자수
				$('#mre_first .letter-count').text(remain);
			}
		}
	});
	/*====================================
	*댓글 삭제
	* ====================================*/
	
	/*====================================
	*초기 데이터(목록) 호출
	* ====================================*/
	selectList(1); //댓글 목록의 function이름
});

내가 댓글을 단 화면
다른 사람이 댓글을 단 화면(수정 삭제가 없음)

detail.jsp에 오타가 있어서 댓글 배경이 회색이 이어져 있던 부분을 수정

댓글이 10개까지 보여진다. 댓글이 11개가 되면 다음글 보기 버튼이 생김

js/board.reply.js(추가)

댓글등록 위에 명시

//페이지 처리 이벤트 연결(다음 댓글 보기 버튼 클릭시 데이터 추가)
	$('.paging-button input').click(function(){
		selectList(currentPage + 1); //다음페이지를 보이게 하는법: 현재페이지에서 1페이지를 증가시킴
	});