댓글을 저장할 수 있는 table 생성
-- 댓글 테이블 
CREATE TABLE freereply(
    rno NUMBER(10,0) primary key, -- 댓글 번호 
    bno NUMBER(10,0), -- 글 번호 (FK)
    reply VARCHAR2(1000), -- 내용 
    reply_id VARCHAR2(50), -- 글쓴이 아이디 
    reply_pw VARCHAR2(50), -- 비번 
    reply_date DATE DEFAULT sysdate, -- 등록일 
    update_date DATE DEFAULT null
);
-- 시퀀스 생성 
CREATE SEQUENCE freereply_seq
    START WITH 1
    INCREMENT BY 1
    MAXVALUE 1000
    NOCYCLE
    NOCACHE;
DB와 연결할 수 있는 Mapper를 생성하기 위해 Interface부터 우선 생성
package com.spring.myweb.reply.mapper;
import java.util.List;
import java.util.Map;
import com.spring.myweb.command.ReplyVO;
public interface IReplyMapper {
	// 댓글 등록 
	void replyRegist(ReplyVO reply);
	
	// 목록 요청
	List<ReplyVO> getList(Map<String, Object> data);
	
	// 댓글 개수 
	int getTotal(int bno);
	
	// 비밀번호 확인 
	int pwCheck(ReplyVO reply);
	
	// 댓글 수정 
	void update(ReplyVO reply);
	
	// 댓글 삭제 
	void delete(int rno);
}
DB와 연결해서 CRUD 수행할 수 있는 Mapper 생성
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  
  
<mapper namespace="com.spring.myweb.reply.mapper.IReplyMapper">
	
	<resultMap type="ReplyVO" id="replyMap">
	<!-- mybatis-config에서 별칭을 지정했으므로 type에 별칭 지정 -->
		
		<result property = "replyId" column = "REPLY_ID"/>
		<result property = "replyPw" column = "REPLY_PW"/>
		<result property = "replyDate" column = "REPLY_DATE"/>
		<result property = "updateDate" column = "UPDATE_DATE"/>
	</resultMap>
	
	<!-- 댓글 등록 -->
	<insert id="replyRegist">
		INSERT INTO freereply(rno, bno, REPLY, REPLY_ID, REPLY_PW)
		VALUES(freereply_seq.NEXTVAL, #{bno}, #{reply}, #{replyId}, #{replyPw})
	</insert>
	
	<!-- 댓글 리스트  -->
	<select id="getList" resultMap = "replyMap">
		SELECT * FROM 
			(SELECT ROWNUM AS rn, tbl.* FROM 	
				(SELECT * FROM freereply
				WHERE bno = #{bno}
				ORDER BY rno DESC
				) tbl
			)
		<![CDATA[
		WHERE rn > (#{paging.pageNum}-1) * #{paging.cpp}
		AND rn <= #{paging.pageNum} * #{paging.cpp}
		]]>
		<!-- getList라는 메서드는 page의 정보가 담긴 PageVO객체와 게시물 번호를 매개변수로 받음 
			 그렇기 때문에 sql에서 해당 정보를 사용할 수 있음 -->
	</select>
	
	<!-- 댓글 개수 -->
	<select id="getTotal" resultType="int">
	<!-- count이므로 반환은 int -->
		SELECT count(*) FROM freereply
		
		WHERE bno = #{bno}
	</select>
	
	<!-- 비밀번호 체크 -->
	<select id="pwCheck" resultType="int">
		SELECT count(*) FROM freereply
		WHERE reply_pw = #{replyPw}
	</select>
	
	<!-- 댓글 수정 -->
	<update id="update" >
		UPDATE freereply SET 
		reply = #{reply}, update_date = sysdate
		WHERE rno = #{rno}
	</update>
	
	<!-- 댓글 삭제 -->
	<delete id="delete">
		DELETE FROM freereply
		WHERE rno = #{rno}
	</delete>
	
	
	
</mapper>
Mybatis가 인식할 수 있도록 root-context.xml에 등록
Beans Graph에서 등록이 제대로 되었는지 확인
<mybatis-spring:scan
		base-package="com.spring.myweb.freeboard.mapper" />
	<mybatis-spring:scan
		base-package="com.spring.myweb.reply.mapper" />
	<mybatis-spring:scan
		base-package="com.spring.myweb.user.mapper" />
	<!-- Mapper인터페이스가 어디에 있는지 경로 지정 -->
	const msg = '${msg}';
	if (msg !== ''){
		alert(msg);
	}
	
	$(document).ready(function() {
		$('#replyRegist').click(function() {
			
	     const content = $('#reply').val();
		 const name = $('#replyId').val();
		 const pw = $('#replyPw').val();
		 const bno = '${board.bno}';
			 // 게시물 번호도 받아와야함 
			 // 어느 게시물의 댓글인지 알 수 있어야하기 때문
			 
			 if (content === '' || name === '' || pw === ''){
				 alert('필수 입력값(내용, 아이디, 비밀번호)의 내용이 비어있습니다. 확인해주세요.');
				 return;
				 // 이벤트 종료 
			 } 
				  // 객체로 만들어줌 
				 console.log(content);
				  $.ajax({
					  type : 'POST',
					  url:'<c:url value ="/reply/replyRegist" />',
					  data : JSON.stringify({
						
							'bno' : bno,
							'reply' : content,
							'replyId' : name,
							'replyPw' : pw
					  }),
					  dataType : 'text', // 서버로 부터 어떤 형식으로 받을지
					  contentType: 'application/json',
					  success: function(result){
						  // 컨트롤러에서 다시 받은 값 
						  console.log('통신 성공' + result);
						  
						  alert('댓글 등록이 완료되었습니다.');
						  $('#reply').val('');
						  $('#replyId').val('');
						  $('#replyPw').val('');
						  // 등록 완료 후 댓글 목록 함수를 호출해서 비동기식으로 목록 표현
						  
						  getList(1, true);
						  
					  },
					  error : function(){
						  alert('댓글 등록 실패');
					  }
				  }); // end ajax
				 
			 
		}); // 댓글 등록 이벤트 끝
		
	 getJSON()
$.getJSON( [요청보낼 url], [서버로 부터 받은 데이터], [통신 성공 여부])
반복문을 통해 서버로부터 받은 데이터를 넣은 html 코드를 작성하여 화면에 뿌려줌
비동기 방식으로 댓글 목록을 서버로부터 가져와서 댓글 목록 출력 진행
  // 더보기 버튼 클릭 처리 
  // 클릭 시 전역 변수인 page에 +1 한 값 전달 
  $('#moreList').click(function() {
      // 더보기 버튼 클릭 했다면
      
      getList(++page, false);
      // page값을 올려주고 reset을 false로 해줌 
      // 왜 false나면 더보기는 댓글을 더 보기 위해서는 누적으로 봐야함
      // 1페이지 댓글 내용에다가 추가로 페이지를 추가해서 댓글 내용을 보여줌
      
  });
  
  // 목록 요청 
  let page = 1; // 페이지 번호 
  let strAdd = ''; // 화면에 그려넣을 태그를 문자열 형태로 추가할 변수 
  
  getList(1, true);
  // 상세보기 화면에 처음 진입했을 때 댓글 리스트 불러옴  
  
  
  // 목록을 불러오는 list
  // getList의 매개값으로 요청된 페이지 번호와 화면을 리셋할 것인지 여부를 bool 타입의 reset이름의 변수로 받음
  // 페이지 이동은 없고 댓글은 밑에 계속 쌓이므로 상황에 따라 페이지를 리셋해서 새롭게 가져올지 누적해서 쌓을 것인지 여부 확인 
  function getList(pageNum, reset) {
      
      // 글번호를 알아야 댓글이 몇개인지 알 수 있음 
      const bno = '${board.bno}';
      
      // 비동기 방식에서 사용하는 함수 
      // getJSON()를 통해서 JSON형식의 파일을 읽어올 수 있음 
      // get방식의 요청을 통해 서버로부터 받은 JSON 데이터를 가져옴 
      // $.getJSON(요청보낼 url, 서버로 받은 데이터, 통신 성공 여부)
      $.getJSON(
              
          "<c:url value='/reply/getList/' />" + bno + '/' + pageNum,
          // 요청을 보냄 
          
          function(data){
              // 요청에 대한 응답의 데이터 
              // 서버가 전송한 데이터는 data에 저장 
              console.log(data);
              
              // data에는 replyList와 total이라는 이름의 데이터가 존재 
              let total = data.total;
              let replyList = data.replyList;
              console.log(replyList);
              
              // insert, update, delete 작업 후에는 댓글을 누적하고 있는 strAdd 변수를 
              // 초기화를 해서 화면이 리셋된 것처럼 보여줘야 함
              if (reset === true){
                  strAdd = '';
                  page = 1;						
              }
              
              // 페이지 번호 * 데이터 수 보다 전체 댓글 개수가 작으면 더보기 버튼을 없앰 (굳이 보여줄 필요가 없음)
              console.log('현재 페이지 : ' + page);
              if (total <= page * 5){
                  // 전체 댓글 개수가 페이지 * 한화면에 보여줄 댓글 수보다 작으면 굳이 더보기 화면을 보여줄 필요가 없음
                  $('#moreList').css('display', 'none');
              } else{
                  $('#moreList').css('display', 'block');
              }
              
              // 응답 데이터의 길이가 0과 같거나 더 작으면 함수를 종료 
              if(replyList.length <= 0){
                  return;
                  // 댓글이 없기 때문에 밑에 있는 반복문 종료 
              }
              
              // 댓글의 개수가 몇개인지 알 수 없으므로 반복문 진행
              // ``을 이용해서 쉽게 삽입 진행
              // controller에서 받은 데이터인 댓글이 들어있는 리스트 데이터 활용
              for (let i =0; i<replyList.length; i++){
                  strAdd += 
                  `<div class='reply-wrap'>
                      <div class='reply-image'>
                          <img src='${pageContext.request.contextPath}/img/profile.png'>
                      </div>
                      <div class='reply-content'>
                          <div class='reply-group'>
                              <strong class='left'>`+ replyList[i].replyId + `</strong> <small class='left'>` + timeStamp(replyList[i].replyDate) + `</small>
                              <a href='` + replyList[i].rno + `' class='right replyDelete'><span class='glyphicon glyphicon-remove'></span>삭제</a>
                              <a href='` + replyList[i].rno + `' class='right replyModify'><span class='glyphicon glyphicon-pencil'></span>수정</a> 
                          </div>
                          <p class='clearfix'>` + replyList[i].reply + `</p>
                      </div>
                  </div>`;
                  // ``을 통해서 한번에 넣어줌
                  // 댓글 영역의 html내용을 가지고 와서 반복문과 ``을 활용해서 필요한 곳에 값을 넣어줌
              } // for 문 끝
              $('#replyList').html(strAdd);
              // 반복문에서 작성한 html을 품을 수 있는 노드를 찾아서 문자열 형식으로 넣어줌
              // 해당 글에 있는 모든 댓글 내용을 추가 
              
          }
          );// end getJSON
          
  }// end getList()
댓글에 걸어놓은 비밀번호를 통해 댓글을 수정할 수 있는 권한 여부 검증
비동기 방식으로 변경 데이터를 서버에 보내 댓글 수정 진행
// 수정 처리 함수 
  // 수정 모달을 열어서 주어 내용을 작성후 수정 내용 작성
  $('#modalModBtn').click(function() {
          
          // 수정에 필요한 값들을 모두 가져옴 
      const rno = $('#modalRno').val();
          // console.log('bno값 :' + bno);
          const replyModi = $('#modalReply').val();
          const replyPw = $('#modalPw').val();
          // console.log(replyModi + replyPw);
          
          
          if (replyModi === '' || replyPw === ''){
              // 값들이 비어있다면 함수 종료 
              alert('내용, 비밀번호를 확인하세요.');
              return;
          }
          
          // ajax 실행 
          // 비동기방식으로 controller에 요청을 보내고 객체형식으로 값을 담아서 보냄 
          $.ajax({
              type:'POST',
              url : '<c:url value ="/reply/update" />',
              data : JSON.stringify({
                  'rno' : rno,
                  'reply' : replyModi,
                  'replyPw' : replyPw
              }),
              dataType:'text',
              contentType : 'application/json',
              
              success:function(data){
                  // 통신이 성공해서 controller로부터 데이터를 받아왔을 때 실행 
                  console.log('통신성공' + data);
                  
                  if (data === 'modSuccess'){
                      alert('댓글 수정 성공 ');
                      // 값 초기화
                      
                      $('#modalReply').val('');
                      $('#modalPw').val('');						 
                      // 사용자가 작성한 값 비우기
                      
                      $('#replyModal').modal('hide');
                      // 모달 창 숨기기
                      
                      getList(1, true);
                      // 새롭게 댓글 불러옴 
                  }
          
                  else{
                      
                      alert('비밀번호가 틀렸습니다.');
                      
                      $('#modalPw').val('');
                      // 비밀번호만 지움
                      
                      $('#modalPw').focus();
                      // 비밀번호 부분에 집중
                  }
              },
              error : function(){
                  // 통신에 실패했을 때 
                  alert("수정에 실패했습니다. 관리자에게 문의해주세요.");
              }
          }); // end ajax
          
  }); // 수정 처리 이벤트 끄읏
댓글에 걸어놓은 비밀번호를 통해 댓글 삭제 권한 여부 검증
비동기 방식으로 삭제하기 위해 필요한 데이터를 서버에 보내 댓글 삭제 진행
// 삭제 함수 
  $('#modalDelBtn').click(function() {
          
          // 삭제 처리를 하기 위해 값을 가져옴 
          const rno = $('#modalRno').val();
          // console.log('bno값 :' + bno);
          const replyPw = $('#modalPw').val();
          
          if (replyPw === ''){
              alert('비밀번호를 입력해주세요.');
              return;
          }
          
          // ajax함수를 활용해서 비동기 통신 방식으로 controller에 데이터 전달 
          $.ajax({
              type:'POST',
              url : '<c:url value="/reply/delete" />',
              data : JSON.stringify({
                  'rno' : rno,
                  'replyPw' : replyPw
              }),
              dataType:'text',
              contentType : 'application/json',
              
              // 통신에 성공해서 controller에서 값을 다시 줬을 때 
              success:function(result){
                  console.log('통신 성공' + result);
                  if (result === 'delSuccess'){
                      alert('댓글 삭제 성공');
                      
                      // 사용자가 작성한 값 비우기 
                      $('#modalReply').val('');
                      $('#modalPw').val('');
                      
                      // 모달 창 숨기기 
                      $('#replyModal').modal('hide');
                      
                      getList(1,true);
                      // 댓글창 다시 불러오기
                  }
                  else{
                      alert('비밀번호가 틀립니다.');
                      $('#modalPw').val('');
                      // 비밀번호 지움 
                      
                      $('#modalPw').focus();
                      // 비밀번호 부분 집중
                  }
              },
              // controller에 값을 받아오지 못했을 때 
              error : function(){
                  alert('삭제에 실패했습니다. 관리자에게 문의해주세요.');
              }
          }); // end ajax
  }); // 삭제 처리 이벤트 끄읏