댓글을 저장할 수 있는 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
}); // 삭제 처리 이벤트 끄읏