07_Framework
( 강사님 풀이 )
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<div id="commentArea">
<!-- 댓글 목록 -->
<div class="comment-list-area">
<ul id="commentList">
<c:forEach items="${board.commentList}" var="comment">
<!-- 부모/자식 댓글 --> <!-- null이 아닌 0으로 작성해줘야 함. -->
<li class="comment-row <c:if test='${comment.parentNo != 0}'>child-comment</c:if>">
<p class="comment-writer">
<!-- 프로필 이미지 -->
<c:if test="${empty comment.profileImage}">
<img src="/resources/images/user.png">
</c:if>
<c:if test="${not empty comment.profileImage}">
<img src="${comment.profileImage}">
</c:if>
<!-- 닉네임 -->
<span>${comment.memberNickname}</span>
<!-- 작성일 -->
<span class="comment-date">${comment.commentCreateDate}</span>
</p>
<!-- 댓글 내용 -->
<p class="comment-content">${comment.commentContent}</p>
<!-- 버튼 영역 -->
<div class="comment-btn-area">
<button onclick="showInsertCommet(${comment.commentNo}, this)">답글</button>
<!-- 로그인한 회원과 댓글 작성자가 같은 경우 -->
<c:if test="${loginMember.memberNo == comment.memberNo}">
<button>수정</button>
<button>삭제</button>
</c:if>
</div>
</li>
</c:forEach>
</ul>
</div>
<!-- 댓글 작성 부분 -->
<div class="comment-write-area">
<textarea id="commentContent"></textarea>
<button id="addComment">
댓글<br>
등록
</button>
</div>
</div>
// 댓글 목록 조회
function selectCommentList(){
// REST(REpresentational State Transfer) API
// - 자원을 이름(주소)으로 구분하여
// 자원의 상태를 주고 받는 것
// -> 주소를 명시하고
// Http Method (GET, POST, PUT, DELETE) 를 이용해
// 지정된 자원에 대한 CRUD 진행
fetch("/comment?boardNo=" + boardNo)
.then(response => response.json())
.then(cList => {
console.log(cList);
// 화면에 출력되어 있는 댓글 목록 삭제
const commentList = document.getElementById("commentList"); // ul태그
commentList.innerHTML = "";
// cList에 저장된 요소를 하나씩 접근
for(let comment of cList){
// 행
const commentRow = document.createElement("li");
commentRow.classList.add("comment-row");
// 답글일 경우 child-comment 클래스 추가
if(comment.parentNo != 0) commentRow.classList.add("child-comment");
// 작성자
const commentWriter = document.createElement("p");
commentWriter.classList.add("comment-writer");
// 프로필 이미지
const profileImage = document.createElement("img");
if( comment.profileImage != null ){ // 프로필 이미지가 있을 경우
profileImage.setAttribute("src", comment.profileImage);
}else{ // 없을 경우 == 기본이미지
profileImage.setAttribute("src", "/resources/images/user.png");
}
// 작성자 닉네임
const memberNickname = document.createElement("span");
memberNickname.innerText = comment.memberNickname;
// 작성일
const commentDate = document.createElement("span");
commentDate.classList.add("comment-date");
commentDate.innerText = "(" + comment.commentCreateDate + ")";
// 작성자 영역(p)에 프로필,닉네임,작성일 마지막 자식으로(append) 추가
commentWriter.append(profileImage , memberNickname , commentDate);
// 댓글 내용
const commentContent = document.createElement("p");
commentContent.classList.add("comment-content");
commentContent.innerHTML = comment.commentContent;
// 행에 작성자, 내용 추가
commentRow.append(commentWriter, commentContent);
// 로그인이 되어있는 경우 답글 버튼 추가
if(loginMemberNo != ""){
// 버튼 영역
const commentBtnArea = document.createElement("div");
commentBtnArea.classList.add("comment-btn-area");
// 답글 버튼
const childCommentBtn = document.createElement("button");
childCommentBtn.setAttribute("onclick", "showInsertComment("+comment.commentNo+", this)");
childCommentBtn.innerText = "답글";
// 버튼 영역에 답글 버튼 추가
commentBtnArea.append(childCommentBtn);
// 로그인한 회원번호와 댓글 작성자의 회원번호가 같을 때만 버튼 추가
if( loginMemberNo == comment.memberNo ){
// 수정 버튼
const updateBtn = document.createElement("button");
updateBtn.innerText = "수정";
// 수정 버튼에 onclick 이벤트 속성 추가
updateBtn.setAttribute("onclick", "showUpdateComment("+comment.commentNo+", this)");
// 삭제 버튼
const deleteBtn = document.createElement("button");
deleteBtn.innerText = "삭제";
// 삭제 버튼에 onclick 이벤트 속성 추가
deleteBtn.setAttribute("onclick", "deleteComment("+comment.commentNo+")");
// 버튼 영역 마지막 자식으로 수정/삭제 버튼 추가
commentBtnArea.append(updateBtn, deleteBtn);
} // if 끝
// 행에 버튼영역 추가
commentRow.append(commentBtnArea);
}
// 댓글 목록(ul)에 행(li)추가
commentList.append(commentRow);
}
})
.catch(err => console.log(err));
}
//-------------------------------------------------------------------------------------------------
// 댓글 등록
const addComment = document.getElementById("addComment");
const commentContent = document.getElementById("commentContent");
addComment.addEventListener("click", e => { // 댓글 등록 버튼이 클릭이 되었을 때
// 1) 로그인이 되어있나? -> 전역변수 memberNo 이용
if(loginMemberNo == ""){ // 로그인 X
alert("로그인 후 이용해주세요.");
return;
}
// 2) 댓글 내용이 작성되어있나?
if(commentContent.value.trim().length == 0){ // 미작성인 경우
alert("댓글을 작성한 후 버튼을 클릭해주세요.");
commentContent.value = ""; // 띄어쓰기, 개행문자 제거
commentContent.focus();
return;
}
// 3) AJAX를 이용해서 댓글 내용 DB에 저장(INSERT)
const data = {"commentContent" : commentContent.value,
"memberNo" : loginMemberNo,
"boardNo" : boardNo
}; // JS객체
fetch("/comment", {
method: "POST",
headers: {"Content-Type" : "application/json"},
body : JSON.stringify(data) // JS 객체 -> JSON 파싱
})
.then(resp => resp.text())
.then(result => {
if(result > 0){ // 등록 성공
alert("댓글이 등록되었습니다.");
commentContent.value = ""; // 작성했던 댓글 삭제
selectCommentList(); // 비동기 댓글 목록 조회 함수 호출
// -> 새로운 댓글이 추가되어짐
} else { // 실패
alert("댓글 등록에 실패했습니다...");
}
})
.catch(err => console.log(err));
});
// -----------------------------------------------------------------------------------
// 댓글 삭제
function deleteComment(commentNo){
if( confirm("정말로 삭제 하시겠습니까?") ){
fetch()
.then()
.then(result => {
if(result > 0){
alert("삭제되었습니다");
selectCommentList(); // 목록을 다시 조회해서 삭제된 글을 제거
}else{
alert("삭제 실패");
}
})
.catch(err => console.log(err));
}
}
// ------------------------------------------------------------------------------------------
// 댓글 수정 화면 전환
let beforeCommentRow; // 수정 전 원래 행의 상태를 저장할 변수
function showUpdateComment(commentNo, btn){
// 댓글번호, 이벤트발생요소(수정버튼)
// ** 댓글 수정이 한 개만 열릴 수 있도록 만들기 **
// 댓글 수정을 위한 textarea를 모두 얻어옴 -> 수정이 활성화 되어 있을 경우 1개, 없으면 0개
const temp = document.getElementsByClassName("update-textarea");
if(temp.length > 0){ // 수정이 한 개 이상 열려 있는 경우
if(confirm("다른 댓글이 수정 중입니다. 현재 댓글을 수정 하시겠습니까?")){ // 확인
temp[0].parentElement.innerHTML = beforeCommentRow;
// comment-row // 백업한 댓글
// 백업 내용으로 덮어 씌워 지면서 textarea 사라짐
}else{ // 취소
return;
}
}
// 1. 댓글 수정이 클릭된 행을 선택
const commentRow = btn.parentElement.parentElement; // 수정 버튼의 부모의 부모
// 2. 행 내용 삭제 전 현재 상태를 저장(백업) (문자열)
// (전역변수 이용)
beforeCommentRow = commentRow.innerHTML;
// 3. 댓글에 작성되어 있던 내용만 얻어오기 -> 새롭게 생성된 textarea 추가될 예정
let beforeContent = commentRow.children[1].innerHTML;
// 이것도 가능!
//let beforeContent = btn.parentElement.previousElementSibling.innerHTML;
// 4. 댓글 행 내부 내용을 모두 삭제
commentRow.innerHTML = "";
// 5. textarea 요소 생성 + 클래스 추가 + **내용 추가**
const textarea = document.createElement("textarea");
textarea.classList.add("update-textarea");
// ******************************************
// XSS 방지 처리 해제
beforeContent = beforeContent.replaceAll("&", "&");
beforeContent = beforeContent.replaceAll("<", "<");
beforeContent = beforeContent.replaceAll(">", ">");
beforeContent = beforeContent.replaceAll(""", "\"");
// ******************************************
textarea.value = beforeContent; // 내용 추가
// 6. commentRow에 생성된 textarea 추가
commentRow.append(textarea);
// 7. 버튼 영역 + 수정/취소 버튼 생성
const commentBtnArea = document.createElement("div");
commentBtnArea.classList.add("comment-btn-area");
const updateBtn = document.createElement("button");
updateBtn.innerText = "수정";
updateBtn.setAttribute("onclick", "updateComment("+commentNo+", this)");
const cancelBtn = document.createElement("button");
cancelBtn.innerText = "취소";
cancelBtn.setAttribute("onclick", "updateCancel(this)");
// 8. 버튼영역에 버튼 추가 후
// commentRow(행)에 버튼영역 추가
commentBtnArea.append(updateBtn, cancelBtn);
commentRow.append(commentBtnArea);
}
// -----------------------------------------------------------------------------------
// 댓글 수정 취소
function updateCancel(btn){
// 매개변수 btn : 클릭된 취소 버튼
// 전역변수 beforeCommentRow : 수정 전 원래 행(댓글)을 저장한 변수
if(confirm("댓글 수정을 취소하시겠습니까?")){
btn.parentElement.parentElement.innerHTML = beforeCommentRow;
}
}
// -----------------------------------------------------------------------------------
// 댓글 수정(AJAX)
function updateComment(commentNo, btn){
// 새로 작성된 댓글 내용 얻어오기
const commentContent = btn.parentElement.previousElementSibling.value;
fetch()
.then()
.then(result => {
if(result > 0){
alert("댓글이 수정되었습니다.");
selectCommentList();
}else{
alert("댓글 수정 실패");
}
})
.catch(err => console.log(err));
}
// -------------------------------------------------------------------------------
// -------------------------------------------------------------------------------
// 답글 작성 화면 추가
// -> 답글 작성 화면은 전체 화면에 1개만 존재 해야한다!
function showInsertComment(parentNo, btn){
// 부모 댓글 번호, 클릭한 답글 버튼
// ** 답글 작성 textarea가 한 개만 열릴 수 있도록 만들기 **
const temp = document.getElementsByClassName("commentInsertContent");
if(temp.length > 0){ // 답글 작성 textara가 이미 화면에 존재하는 경우
if(confirm("다른 답글을 작성 중입니다. 현재 댓글에 답글을 작성 하시겠습니까?")){
temp[0].nextElementSibling.remove(); // 버튼 영역부터 삭제
temp[0].remove(); // textara 삭제 (기준점은 마지막에 삭제해야 된다!)
} else{
return; // 함수를 종료시켜 답글이 생성되지 않게함.
}
}
// 답글을 작성할 textarea 요소 생성
const textarea = document.createElement("textarea");
textarea.classList.add("commentInsertContent");
// 답글 버튼의 부모의 뒤쪽에 textarea 추가
// after(요소) : 뒤쪽에 추가
btn.parentElement.after(textarea);
// 답글 버튼 영역 + 등록/취소 버튼 생성 및 추가
const commentBtnArea = document.createElement("div");
commentBtnArea.classList.add("comment-btn-area");
const insertBtn = document.createElement("button");
insertBtn.innerText = "등록";
insertBtn.setAttribute("onclick", "insertChildComment("+parentNo+", this)");
const cancelBtn = document.createElement("button");
cancelBtn.innerText = "취소";
cancelBtn.setAttribute("onclick", "insertCancel(this)");
// 답글 버튼 영역의 자식으로 등록/취소 버튼 추가
commentBtnArea.append(insertBtn, cancelBtn);
// 답글 버튼 영역을 화면에 추가된 textarea 뒤쪽에 추가
textarea.after(commentBtnArea);
}
// 답글 취소
function insertCancel(btn){
// 취소
btn.parentElement.previousElementSibling.remove(); // 취소의 부모의 이전 요소(textarea) 제거
btn.parentElement.remove(); // 취소의 부모 요소(comment-btn-area) 제거
}
// 답글 등록
function insertChildComment(parentNo, btn){
// 부모 댓글 번호, 답글 등록 버튼
// 누가? loginMemberNo(로그인한 회원의 memberNo )(전역변수)
// 어떤 내용? textarea에 작성된 내용
// 몇번 게시글? 현재 게시글 boardNo (전역변수)
// 부모 댓글은 누구? parentNo (매개변수)
// 답글 내용
const commentContent = btn.parentElement.previousElementSibling.value;
// 답글 내용이 작성되지 않은 경우
if(commentContent.trim().length == 0){
alert("답글 작성 후 등록 버튼을 클릭해주세요.");
btn.parentElement.previousElementSibling.value = "";
btn.parentElement.previousElementSibling.focus();
return;
}
const data = {"commentContent" : commentContent,
"memberNo" : loginMemberNo,
"boardNo" : boardNo,
"parentNo" : parentNo}; // JS객체
fetch("/comment", {
method: "POST",
headers: {"Content-Type" : "application/json"},
body : JSON.stringify(data) // JS 객체 -> JSON 파싱
})
.then(resp => resp.text())
.then(result => {
if(result > 0){ // 등록 성공
alert("답글이 등록되었습니다.");
selectCommentList(); // 비동기 댓글 목록 조회 함수 호출
} else { // 실패
alert("답글 등록에 실패했습니다...");
}
})
.catch(err => console.log(err));
}
package edu.kh.project.board.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import edu.kh.project.board.model.dto.Comment;
import edu.kh.project.board.model.service.CommentService;
// @Controller + @ResponseBody
@RestController // 요청/응답 처리(단, 모든 요청 응답은 비동기)
// -> REST API를 구축하기 위한 Controller
public class CommentController {
@Autowired
private CommentService service;
// 댓글 목록 조회 // json 통신 시 한글깨짐 방지
@GetMapping(value="/comment", produces="application/json; charset=UTF-8")
public List<Comment> select(int boardNo) {
return service.select(boardNo);
// 동기 시 return : forward / redirect
// 비동기 시 return : 값 자체
}
// 댓글 삽입
@PostMapping("/comment")
public int insert(@RequestBody Comment comment) {
// 요청 데이터(JSON)
// HttpMessageConverter가 해석 -> Java 객체(comment)에 대입
return service.insert(comment);
}
// 댓글 삭제
// 댓글 수정
}
package edu.kh.project.board.model.service;
import java.util.List;
import java.util.Map;
import edu.kh.project.board.model.dto.Comment;
public interface CommentService {
/** 댓글 목록 조회
* @param boardNo
* @return List
*/
List<Comment> select(int boardNo);
/** 댓글 삽입
* @param comment
* @return result
*/
int insert(Comment comment);
}
package edu.kh.project.board.model.service;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import edu.kh.project.board.model.dao.CommentDAO;
import edu.kh.project.board.model.dto.Comment;
import edu.kh.project.common.utility.Util;
@Service
public class CommentServiceImpl implements CommentService{
@Autowired
private CommentDAO dao;
// 댓글 목록 조회
@Override
public List<Comment> select(int boardNo) {
return dao.select(boardNo);
}
// 댓글 삽입
@Transactional(rollbackFor = Exception.class)
@Override
public int insert(Comment comment) {
// XSS 방지 처리
comment.setCommentContent( Util.XSSHandling(comment.getCommentContent()) );
return dao.insert(comment);
}
}
package edu.kh.project.board.model.dao;
import java.util.List;
import java.util.Map;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import edu.kh.project.board.model.dto.Comment;
@Repository
public class CommentDAO {
@Autowired
private SqlSessionTemplate sqlSession;
/** 댓글 목록 조회
* @param boardNo
* @return List
*/
public List<Comment> select(int boardNo) {
return sqlSession.selectList("boardMapper.selectCommentList", boardNo);
}
/** 댓글 삽입
* @param comment
* @return result
*/
public int insert(Comment comment) {
return sqlSession.insert("commentMapper.insert", comment);
}
}
<?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="boardMapper">
<!-- Board DTO에 대한 resultMap -->
<resultMap type="Board" id="board_rm">
<id property="boardNo" column="BOARD_NO"/>
<result property="boardTitle" column="BOARD_TITLE"/>
<result property="boardContent" column="BOARD_CONTENT"/>
<result property="boardCreateDate" column="B_CREATE_DATE"/>
<result property="boardUpdateDate" column="B_UPDATE_DATE"/>
<result property="readCount" column="READ_COUNT"/>
<result property="commentCount" column="COMMENT_COUNT"/>
<result property="likeCount" column="LIKE_COUNT"/>
<result property="memberNickname" column="MEMBER_NICKNAME"/>
<result property="memberNo" column="MEMBER_NO"/>
<result property="profileImage" column="PROFILE_IMG"/>
<result property="thumbnail" column="THUMBNAIL"/>
<!-- collection 태그
select로 조회된 결과를 컬렉션(List)에 담아
지정된 필드에 세팅
property : List를 담을 DTO의 필드명
select : 실행할 select의 id
column : 조회 결과 중 지정된 컬럼의 값을 파라미터로 전달
javaType : List(컬렉션)의 타입을 지정
ofType : List(컬렉션)의 제네릭(타입 제한) 지정
-->
<collection property="imageList"
select="selectImageList"
column="BOARD_NO"
javaType="java.util.ArrayList"
ofType="BoardImage"
/>
<collection property="commentList"
select="selectCommentList"
column="BOARD_NO"
javaType="java.util.ArrayList"
ofType="Comment"
/>
</resultMap>
<!-- BoardImage resultMap -->
<resultMap type="BoardImage" id="boardImage_rm">
<id property="imageNo" column="IMG_NO"/>
<result property="imagePath" column="IMG_PATH"/>
<result property="imageReName" column="IMG_RENAME"/>
<result property="imageOriginal" column="IMG_ORIGINAL"/>
<result property="imageOrder" column="IMG_ORDER"/>
<result property="boardNo" column="BOARD_NO"/>
</resultMap>
<!-- Comment resultMap -->
<resultMap type="Comment" id="comment_rm">
<id property="commentNo" column="COMMENT_NO"/>
<result property="commentContent" column="COMMENT_CONTENT"/>
<result property="commentCreateDate" column="C_CREATE_DATE"/>
<result property="boardNo" column="BOARD_NO"/>
<result property="memberNo" column="MEMBER_NO"/>
<result property="commentDeleteFlag" column="COMMENT_DEL_FL"/>
<result property="parentNo" column="PARENT_NO"/>
<result property="memberNickname" column="MEMBER_NICKNAME"/>
<result property="profileImage" column="PROFILE_IMG"/>
</resultMap>
<!--
resultType이 "map"인 경우
K : 컬럼명(BOARD_CODE, BOARD_NAME)
V : 컬럼 값( 1 , 공지 사항 )
[{BOARD_NAME= 공지사항, BOARD_CODE=1}, {BOARD_NAME= 자유게시판, BOARD_CODE=2}, ...]
-->
<!-- 게시판 종류 목록 조회 -->
<select id="selectBoardTypeList" resultType="map">
SELECT * FROM "BOARD_TYPE" ORDER BY 1
</select>
<!-- 특정 게시판의 삭제되지 않은 게시글 수 조회 -->
<select id="getListCount" resultType="_int">
SELECT COUNT(*) FROM BOARD
WHERE BOARD_DEL_FL = 'N'
AND BOARD_CODE = #{boardCode}
</select>
<!-- CDATA 태그 : 해당 태그 내부에 작성된 것은 모두 문자로 취급 -->
<select id="selectBoardList" resultMap="board_rm">
SELECT BOARD_NO, BOARD_TITLE, MEMBER_NICKNAME, READ_COUNT,
<![CDATA[
CASE
WHEN SYSDATE - B_CREATE_DATE < 1/24/60
THEN FLOOR( (SYSDATE - B_CREATE_DATE) * 24 * 60 * 60 ) || '초 전'
WHEN SYSDATE - B_CREATE_DATE < 1/24
THEN FLOOR( (SYSDATE - B_CREATE_DATE) * 24 * 60) || '분 전'
WHEN SYSDATE - B_CREATE_DATE < 1
THEN FLOOR( (SYSDATE - B_CREATE_DATE) * 24) || '시간 전'
ELSE TO_CHAR(B_CREATE_DATE, 'YYYY-MM-DD')
END B_CREATE_DATE,
]]>
(SELECT COUNT(*) FROM "COMMENT" C
WHERE C.BOARD_NO = B.BOARD_NO) COMMENT_COUNT,
(SELECT COUNT(*) FROM BOARD_LIKE L
WHERE L.BOARD_NO = B.BOARD_NO) LIKE_COUNT,
(SELECT IMG_PATH || IMG_RENAME FROM BOARD_IMG I
WHERE I.BOARD_NO = B.BOARD_NO
AND IMG_ORDER = 0) THUMBNAIL
FROM "BOARD" B
JOIN "MEMBER" USING(MEMBER_NO)
WHERE BOARD_DEL_FL = 'N'
AND BOARD_CODE = #{boardCode}
ORDER BY BOARD_NO DESC
</select>
<select id="selectBoard" resultMap="board_rm">
SELECT BOARD_NO, BOARD_TITLE, BOARD_CONTENT, BOARD_CODE,
READ_COUNT, MEMBER_NICKNAME, MEMBER_NO, PROFILE_IMG,
TO_CHAR(B_CREATE_DATE, 'YYYY"년" MM"월" DD"일" HH24:MI:SS') B_CREATE_DATE,
TO_CHAR(B_UPDATE_DATE, 'YYYY"년" MM"월" DD"일" HH24:MI:SS') B_UPDATE_DATE,
(SELECT COUNT(*)
FROM "BOARD_LIKE" L
WHERE L.BOARD_NO = B.BOARD_NO) LIKE_COUNT
FROM "BOARD" B
JOIN "MEMBER" USING(MEMBER_NO)
WHERE BOARD_DEL_FL = 'N'
AND BOARD_CODE = #{boardCode}
AND BOARD_NO = #{boardNo}
</select>
<!-- 특정 게시글 이미지 조회 -->
<select id="selectImageList" resultMap="boardImage_rm">
SELECT * FROM BOARD_IMG
WHERE BOARD_NO = #{boardNo}
ORDER BY IMG_ORDER
</select>
<!-- 특정 게시글 댓글 조회(바뀔 예정) -->
<select id="selectCommentList" resultMap="comment_rm">
SELECT LEVEL, C.* FROM
(SELECT COMMENT_NO, COMMENT_CONTENT,
TO_CHAR(C_CREATE_DATE, 'YYYY"년" MM"월" DD"일" HH24"시" MI"분" SS"초"') C_CREATE_DATE,
BOARD_NO, MEMBER_NO, MEMBER_NICKNAME, PROFILE_IMG, PARENT_NO, COMMENT_DEL_FL
FROM "COMMENT"
JOIN MEMBER USING(MEMBER_NO)
WHERE BOARD_NO = #{boardNo}) C
WHERE COMMENT_DEL_FL = 'N'
START WITH PARENT_NO IS NULL
CONNECT BY PRIOR COMMENT_NO = PARENT_NO
ORDER SIBLINGS BY COMMENT_NO
</select>
<!-- 좋아요 여부 확인 -->
<select id="boardLikeCheck" resultType="_int">
SELECT COUNT(*) FROM BOARD_LIKE
WHERE BOARD_NO = #{boardNo}
AND MEMBER_NO = #{memberNo}
</select>
<!-- 조회 수 증가 -->
<update id="updateReadCount">
UPDATE "BOARD" SET
READ_COUNT = READ_COUNT + 1
WHERE BOARD_NO = #{boardNo}
</update>
<!-- 좋아요 삽입 -->
<insert id="insertBoardLike">
INSERT INTO "BOARD_LIKE" VALUES (#{boardNo}, #{memberNo})
</insert>
<!-- 좋아요 삭제 -->
<delete id="deleteBoardLike">
DELETE FROM "BOARD_LIKE"
WHERE BOARD_NO = #{boardNo}
AND MEMBER_NO = #{memberNo}
</delete>
<!-- 좋아요 조회 -->
<select id="countBoardLike" resultType="_int">
SELECT COUNT(*) FROM "BOARD_LIKE" WHERE BOARD_NO = #{boardNo}
</select>
<!-- 게시글 삽입 -->
<!--
동적 SQL
- 프로그램 수행 중 SQL을 변경하는 기능
<selectKey> 태그 : INSERT/UPDATE 시 사용할 키(시퀀스)를
조회해서 파라미터의 지정된 필드 대입
useGeneratedKeys : DB 내부적으로 생성한키 (시퀀스) 를
전달된 파라미터의 필드로 대입 가능 여부 지정
order="BEFORE" : 메인 SQL이 수행되기 전에 selectKey가 수행되도록 지정
keyProperty : selectKey 조회 결과를 저장할 파라미터의 필드
Board의 boardNo 필드에다가 조회 결과를 저장하겠다.
-->
<insert id="boardInsert" parameterType="Board" useGeneratedKeys="true">
<selectKey order="BEFORE" resultType="_int" keyProperty="boardNo">
SELECT SEQ_BOARD_NO.NEXTVAL FROM DUAL
</selectKey>
INSERT INTO BOARD
VALUES( #{boardNo},
#{boardTitle},
#{boardContent},
DEFAULT, DEFAULT, DEFAULT, DEFAULT,
#{memberNo},
#{boardCode} )
</insert>
<!--
동적 SQL 중 <foreach>
- 특정 sql 구문을 반복할 때 사용
- 반복되는 사이에 구분자를 추가할 수 있음.
-->
<!-- 이미지 리스트(여러개)삽입 -->
<insert id="insertImageList" parameterType="list">
INSERT INTO "BOARD_IMG"
SELECT SEQ_IMG_NO.NEXTVAL, A.*
FROM (
<foreach collection="list" item="img" separator=" UNION ALL ">
SELECT #{img.imagePath} IMG_PATH,
#{img.imageReName} IMG_RENAME,
#{img.imageOriginal} IMG_ORIGINAL,
#{img.imageOrder} IMG_ORDER,
#{img.boardNo} BOARD_NO
FROM DUAL
</foreach>
) A
</insert>
<!-- 게시글 수정 -->
<update id="boardUpdate">
UPDATE "BOARD" SET
BOARD_TITLE = #{boardTitle},
BOARD_CONTENT = #{boardContent},
B_UPDATE_DATE = SYSDATE
WHERE BOARD_CODE = #{boardCode}
AND BOARD_NO = #{boardNo}
</update>
<!-- 이미지 삭제 -->
<delete id="imageDelete">
DELETE FROM "BOARD_IMG"
WHERE BOARD_NO = #{boardNo}
AND IMG_ORDER IN ( ${deleteList} )
</delete>
<!-- 이미지 수정 -->
<update id="imageUpdate">
UPDATE "BOARD_IMG" SET
IMG_PATH = #{imagePath},
IMG_ORIGINAL = #{imageOriginal},
IMG_RENAME = #{imageReName}
WHERE BOARD_NO = #{boardNo}
AND IMG_ORDER = #{imageOrder}
</update>
<!-- 이미지 삽입 -->
<insert id="imageInsert">
INSERT INTO "BOARD_IMG"
VALUES(SEQ_IMG_NO.NEXTVAL, #{imagePath}, #{imageReName},
#{imageOriginal}, #{imageOrder}, #{boardNo}
)
</insert>
<!-- 게시글 삭제 -->
<update id="boardDelete">
UPDATE BOARD SET
BOARD_DEL_FL = 'Y'
WHERE BOARD_CODE = #{boardCode}
AND BOARD_NO = #{boardNo}
</update>
</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="commentMapper">
<!-- 댓글 삽입 -->
<insert id="insert">
INSERT INTO "COMMENT"
VALUES(SEQ_COMMENT_NO.NEXTVAL,
#{commentContent},
DEFAULT, DEFAULT,
#{boardNo}, #{memberNo},
<!-- 동적 sql : if문 -->
<!-- 부모 댓글 -->
<if test="parentNo == 0"> NULL </if>
<!-- 자식 댓글 -->
<if test="parentNo != 0"> #{parentNo} </if>
)
</insert>
</mapper>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd" >
<configuration>
<!-- SqlSessionTemplate 관련 설정 -->
<settings>
<!-- insert, update 사용 값중 null 이 있을 경우
SQL 구문에 null 포함되어 있다는 오류 발생
이 설정 후, 오류 발생 X, NULL 값을 컬럼에 대입
단, NOT NULL 제약조건이 없는 컬럼에만 가능
-->
<setting name="jdbcTypeForNull" value="NULL"/>
</settings>
<!-- 별칭 작성 부분 -->
<!-- VO/DTO 클래스의 패키지명+클래스명 작성하는게 불편하기 때문에 짧은 별칭을 부여 -->
<typeAliases>
<typeAlias type="edu.kh.project.member.model.dto.Member" alias="Member"/>
<typeAlias type="edu.kh.project.board.model.dto.Board" alias="Board"/>
<typeAlias type="edu.kh.project.board.model.dto.BoardImage" alias="BoardImage"/>
<typeAlias type="edu.kh.project.board.model.dto.Comment" alias="Comment"/>
</typeAliases>
<!-- mapper파일(SQL 작성되는파일) 위치 등록 부분 -->
<mappers>
<mapper resource="/mappers/member-mapper.xml"/>
<mapper resource="/mappers/ajax-mapper.xml"/>
<mapper resource="/mappers/email-mapper.xml"/>
<mapper resource="/mappers/myPage-mapper.xml"/>
<mapper resource="/mappers/board-mapper.xml"/>
<mapper resource="/mappers/comment-mapper.xml"/>
<!-- 추후 board-mapper를 사용하고 싶다면 추가해야 함!
<mapper resource="/mappers/board-mapper.xml"/>
-->
</mappers>
</configuration>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<div id="commentArea">
<!-- 댓글 목록 -->
<div class="comment-list-area">
<ul id="commentList">
<c:forEach items="${board.commentList}" var="comment">
<!-- 부모/자식 댓글 --> <!-- null이 아닌 0으로 작성해줘야 함. -->
<li class="comment-row <c:if test='${comment.parentNo != 0}'>child-comment</c:if>">
<p class="comment-writer">
<!-- 프로필 이미지 -->
<c:if test="${empty comment.profileImage}">
<img src="/resources/images/user.png">
</c:if>
<c:if test="${not empty comment.profileImage}">
<img src="${comment.profileImage}">
</c:if>
<!-- 닉네임 -->
<span>${comment.memberNickname}</span>
<!-- 작성일 -->
<span class="comment-date">${comment.commentCreateDate}</span>
</p>
<!-- 댓글 내용 -->
<p class="comment-content">${comment.commentContent}</p>
<!-- 버튼 영역 -->
<div class="comment-btn-area">
<button onclick="showInsertComment(${comment.commentNo}, this)">답글</button>
<!-- 로그인한 회원과 댓글 작성자가 같은 경우 -->
<c:if test="${loginMember.memberNo == comment.memberNo}">
<button onclick="showUpdateComment(${comment.commentNo}, this)">수정</button>
<button onclick="deleteComment(${comment.commentNo})">삭제</button>
</c:if>
</div>
</li>
</c:forEach>
</ul>
</div>
<!-- 댓글 작성 부분 -->
<div class="comment-write-area">
<textarea id="commentContent"></textarea>
<button id="addComment">
댓글<br>
등록
</button>
</div>
</div>
// 댓글 목록 조회
function selectCommentList(){
// REST(REpresentational State Transfer) API
// - 자원을 이름(주소)으로 구분하여
// 자원의 상태를 주고 받는 것
// -> 주소를 명시하고
// Http Method (GET, POST, PUT, DELETE) 를 이용해
// 지정된 자원에 대한 CRUD 진행
fetch("/comment?boardNo=" + boardNo)
.then(resp => resp.json())
.then(cList => {
console.log(cList);
// 화면에 출력되어 있는 댓글 목록 삭제
const commentList = document.getElementById("commentList"); // ul태그
commentList.innerHTML = "";
// cList에 저장된 요소를 하나씩 접근
for(let comment of cList){
// 행
const commentRow = document.createElement("li");
commentRow.classList.add("comment-row");
// 답글일 경우 child-comment 클래스 추가
if(comment.parentNo != 0) commentRow.classList.add("child-comment");
// 작성자
const commentWriter = document.createElement("p");
commentWriter.classList.add("comment-writer");
// 프로필 이미지
const profileImage = document.createElement("img");
if( comment.profileImage != null ){ // 프로필 이미지가 있을 경우
profileImage.setAttribute("src", comment.profileImage);
}else{ // 없을 경우 == 기본이미지
profileImage.setAttribute("src", "/resources/images/user.png");
}
// 작성자 닉네임
const memberNickname = document.createElement("span");
memberNickname.innerText = comment.memberNickname;
// 작성일
const commentDate = document.createElement("span");
commentDate.classList.add("comment-date");
commentDate.innerText = "(" + comment.commentCreateDate + ")";
// 작성자 영역(p)에 프로필,닉네임,작성일 마지막 자식으로(append) 추가
commentWriter.append(profileImage , memberNickname , commentDate);
// 댓글 내용
const commentContent = document.createElement("p");
commentContent.classList.add("comment-content");
commentContent.innerHTML = comment.commentContent;
// 행에 작성자, 내용 추가
commentRow.append(commentWriter, commentContent);
// 로그인이 되어있는 경우 답글 버튼 추가
if(loginMemberNo != ""){
// 버튼 영역
const commentBtnArea = document.createElement("div");
commentBtnArea.classList.add("comment-btn-area");
// 답글 버튼
const childCommentBtn = document.createElement("button");
childCommentBtn.setAttribute("onclick", "showInsertComment("+comment.commentNo+", this)");
childCommentBtn.innerText = "답글";
// 버튼 영역에 답글 버튼 추가
commentBtnArea.append(childCommentBtn);
// 로그인한 회원번호와 댓글 작성자의 회원번호가 같을 때만 버튼 추가
if( loginMemberNo == comment.memberNo ){
// 수정 버튼
const updateBtn = document.createElement("button");
updateBtn.innerText = "수정";
// 수정 버튼에 onclick 이벤트 속성 추가
updateBtn.setAttribute("onclick", "showUpdateComment("+comment.commentNo+", this)");
// 삭제 버튼
const deleteBtn = document.createElement("button");
deleteBtn.innerText = "삭제";
// 삭제 버튼에 onclick 이벤트 속성 추가
deleteBtn.setAttribute("onclick", "deleteComment("+comment.commentNo+")");
// 버튼 영역 마지막 자식으로 수정/삭제 버튼 추가
commentBtnArea.append(updateBtn, deleteBtn);
} // if 끝
// 행에 버튼영역 추가
commentRow.append(commentBtnArea);
}
// 댓글 목록(ul)에 행(li)추가
commentList.append(commentRow);
}
})
.catch(err => console.log(err));
}
//-------------------------------------------------------------------------------------------------
// 댓글 등록
const addComment = document.getElementById("addComment");
const commentContent = document.getElementById("commentContent");
addComment.addEventListener("click", e => { // 댓글 등록 버튼이 클릭이 되었을 때
// 1) 로그인이 되어있나? -> 전역변수 memberNo 이용
if(loginMemberNo == ""){ // 로그인 X
alert("로그인 후 이용해주세요.");
return;
}
// 2) 댓글 내용이 작성되어있나?
if(commentContent.value.trim().length == 0){ // 미작성인 경우
alert("댓글을 작성한 후 버튼을 클릭해주세요.");
commentContent.value = ""; // 띄어쓰기, 개행문자 제거
commentContent.focus();
return;
}
// 3) AJAX를 이용해서 댓글 내용 DB에 저장(INSERT)
const data = {"commentContent" : commentContent.value,
"memberNo" : loginMemberNo,
"boardNo" : boardNo
}; // JS객체
fetch("/comment", {
method: "POST",
headers: {"Content-Type" : "application/json"},
body : JSON.stringify(data) // JS 객체 -> JSON 파싱
})
.then(resp => resp.text())
.then(result => {
if(result > 0){ // 등록 성공
alert("댓글이 등록되었습니다.");
commentContent.value = ""; // 작성했던 댓글 삭제
selectCommentList(); // 비동기 댓글 목록 조회 함수 호출
// -> 새로운 댓글이 추가되어짐
} else { // 실패
alert("댓글 등록에 실패했습니다...");
}
})
.catch(err => console.log(err));
});
// -----------------------------------------------------------------------------------
// 댓글 삭제
function deleteComment(commentNo){
if( confirm("정말로 삭제 하시겠습니까?") ){
fetch("/comment/delete?commentNo=" + commentNo)
.then(resp => resp.text())
.then(result => {
if(result > 0){
alert("삭제되었습니다");
selectCommentList(); // 목록을 다시 조회해서 삭제된 글을 제거
}else{
alert("삭제 실패");
}
})
.catch(err => console.log(err));
}
}
// ------------------------------------------------------------------------------------------
// 댓글 수정 화면 전환
let beforeCommentRow; // 수정 전 원래 행의 상태를 저장할 변수
function showUpdateComment(commentNo, btn){
// 댓글번호, 이벤트발생요소(수정버튼)
// ** 댓글 수정이 한 개만 열릴 수 있도록 만들기 **
// 댓글 수정을 위한 textarea를 모두 얻어옴 -> 수정이 활성화 되어 있을 경우 1개, 없으면 0개
const temp = document.getElementsByClassName("update-textarea");
if(temp.length > 0){ // 수정이 한 개 이상 열려 있는 경우
if(confirm("다른 댓글이 수정 중입니다. 현재 댓글을 수정 하시겠습니까?")){ // 확인
temp[0].parentElement.innerHTML = beforeCommentRow;
// comment-row // 백업한 댓글
// 백업 내용으로 덮어 씌워 지면서 textarea 사라짐
}else{ // 취소
return;
}
}
// 1. 댓글 수정이 클릭된 행을 선택
const commentRow = btn.parentElement.parentElement; // 수정 버튼의 부모의 부모
// 2. 행 내용 삭제 전 현재 상태를 저장(백업) (문자열)
// (전역변수 이용)
beforeCommentRow = commentRow.innerHTML;
// 3. 댓글에 작성되어 있던 내용만 얻어오기 -> 새롭게 생성된 textarea 추가될 예정
let beforeContent = commentRow.children[1].innerHTML;
// 이것도 가능!
//let beforeContent = btn.parentElement.previousElementSibling.innerHTML;
// 4. 댓글 행 내부 내용을 모두 삭제
commentRow.innerHTML = "";
// 5. textarea 요소 생성 + 클래스 추가 + **내용 추가**
const textarea = document.createElement("textarea");
textarea.classList.add("update-textarea");
// ******************************************
// XSS 방지 처리 해제
beforeContent = beforeContent.replaceAll("&", "&");
beforeContent = beforeContent.replaceAll("<", "<");
beforeContent = beforeContent.replaceAll(">", ">");
beforeContent = beforeContent.replaceAll(""", "\"");
// ******************************************
textarea.value = beforeContent; // 내용 추가
// 6. commentRow에 생성된 textarea 추가
commentRow.append(textarea);
// 7. 버튼 영역 + 수정/취소 버튼 생성
const commentBtnArea = document.createElement("div");
commentBtnArea.classList.add("comment-btn-area");
const updateBtn = document.createElement("button");
updateBtn.innerText = "수정";
updateBtn.setAttribute("onclick", "updateComment("+commentNo+", this)");
const cancelBtn = document.createElement("button");
cancelBtn.innerText = "취소";
cancelBtn.setAttribute("onclick", "updateCancel(this)");
// 8. 버튼영역에 버튼 추가 후
// commentRow(행)에 버튼영역 추가
commentBtnArea.append(updateBtn, cancelBtn);
commentRow.append(commentBtnArea);
}
// -----------------------------------------------------------------------------------
// 댓글 수정 취소
function updateCancel(btn){
// 매개변수 btn : 클릭된 취소 버튼
// 전역변수 beforeCommentRow : 수정 전 원래 행(댓글)을 저장한 변수
if(confirm("댓글 수정을 취소하시겠습니까?")){
btn.parentElement.parentElement.innerHTML = beforeCommentRow;
}
}
// -----------------------------------------------------------------------------------
// 댓글 수정(AJAX)
function updateComment(commentNo, btn){
// 새로 작성된 댓글 내용 얻어오기
const commentContent = btn.parentElement.previousElementSibling.value;
const data = {
"commentNo" : commentNo,
"commentContent" : commentContent
}; // JS객체
fetch("/comment/update", {
method: "POST",
headers: {"Content-Type" : "application/json"},
body : JSON.stringify(data) // JS 객체 -> JSON 파싱
})
.then(resp => resp.text())
.then(result => {
if(result > 0){
alert("댓글이 수정되었습니다.");
selectCommentList();
}else{
alert("댓글 수정 실패");
}
})
.catch(err => console.log(err));
}
// -------------------------------------------------------------------------------
// -------------------------------------------------------------------------------
// 답글 작성 화면 추가
// -> 답글 작성 화면은 전체 화면에 1개만 존재 해야한다!
function showInsertComment(parentNo, btn){
// 부모 댓글 번호, 클릭한 답글 버튼
// ** 답글 작성 textarea가 한 개만 열릴 수 있도록 만들기 **
const temp = document.getElementsByClassName("commentInsertContent");
if(temp.length > 0){ // 답글 작성 textara가 이미 화면에 존재하는 경우
if(confirm("다른 답글을 작성 중입니다. 현재 댓글에 답글을 작성 하시겠습니까?")){
temp[0].nextElementSibling.remove(); // 버튼 영역부터 삭제
temp[0].remove(); // textara 삭제 (기준점은 마지막에 삭제해야 된다!)
} else{
return; // 함수를 종료시켜 답글이 생성되지 않게함.
}
}
// 답글을 작성할 textarea 요소 생성
const textarea = document.createElement("textarea");
textarea.classList.add("commentInsertContent");
// 답글 버튼의 부모의 뒤쪽에 textarea 추가
// after(요소) : 뒤쪽에 추가
btn.parentElement.after(textarea);
// 답글 버튼 영역 + 등록/취소 버튼 생성 및 추가
const commentBtnArea = document.createElement("div");
commentBtnArea.classList.add("comment-btn-area");
const insertBtn = document.createElement("button");
insertBtn.innerText = "등록";
insertBtn.setAttribute("onclick", "insertChildComment("+parentNo+", this)");
const cancelBtn = document.createElement("button");
cancelBtn.innerText = "취소";
cancelBtn.setAttribute("onclick", "insertCancel(this)");
// 답글 버튼 영역의 자식으로 등록/취소 버튼 추가
commentBtnArea.append(insertBtn, cancelBtn);
// 답글 버튼 영역을 화면에 추가된 textarea 뒤쪽에 추가
textarea.after(commentBtnArea);
}
// 답글 취소
function insertCancel(btn){
// 취소
btn.parentElement.previousElementSibling.remove(); // 취소의 부모의 이전 요소(textarea) 제거
btn.parentElement.remove(); // 취소의 부모 요소(comment-btn-area) 제거
}
// 답글 등록
function insertChildComment(parentNo, btn){
// 부모 댓글 번호, 답글 등록 버튼
// 누가? loginMemberNo(로그인한 회원의 memberNo )(전역변수)
// 어떤 내용? textarea에 작성된 내용
// 몇번 게시글? 현재 게시글 boardNo (전역변수)
// 부모 댓글은 누구? parentNo (매개변수)
// 답글 내용
const commentContent = btn.parentElement.previousElementSibling.value;
// 답글 내용이 작성되지 않은 경우
if(commentContent.trim().length == 0){
alert("답글 작성 후 등록 버튼을 클릭해주세요.");
btn.parentElement.previousElementSibling.value = "";
btn.parentElement.previousElementSibling.focus();
return;
}
const data = {"commentContent" : commentContent,
"memberNo" : loginMemberNo,
"boardNo" : boardNo,
"parentNo" : parentNo}; // JS객체
fetch("/comment", {
method: "POST",
headers: {"Content-Type" : "application/json"},
body : JSON.stringify(data) // JS 객체 -> JSON 파싱
})
.then(resp => resp.text())
.then(result => {
if(result > 0){ // 등록 성공
alert("답글이 등록되었습니다.");
selectCommentList(); // 비동기 댓글 목록 조회 함수 호출
} else { // 실패
alert("답글 등록에 실패했습니다...");
}
})
.catch(err => console.log(err));
}
package edu.kh.project.board.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import edu.kh.project.board.model.dto.Comment;
import edu.kh.project.board.model.service.CommentService;
// @Controller + @ResponseBody
@RestController // 요청/응답 처리(단, 모든 요청 응답은 비동기)
// -> REST API를 구축하기 위한 Controller
public class CommentController {
@Autowired
private CommentService service;
// 댓글 목록 조회 // json 통신 시 한글깨짐 방지
@GetMapping(value="/comment", produces="application/json; charset=UTF-8")
public List<Comment> select(int boardNo) {
return service.select(boardNo);
// 동기 시 return : forward / redirect
// 비동기 시 return : 값 자체
}
// 댓글 삽입
@PostMapping("/comment")
public int insert(@RequestBody Comment comment) {
// 요청 데이터(JSON)
// HttpMessageConverter가 해석 -> Java 객체(comment)에 대입
return service.insert(comment);
}
// 댓글 삭제
@GetMapping("/comment/delete")
public int delete(int commentNo) {
return service.delete(commentNo);
}
// 댓글 수정
@PostMapping("/comment/update")
public int update(@RequestBody Comment comment) {
return service.update(comment);
}
}
package edu.kh.project.board.model.service;
import java.util.List;
import java.util.Map;
import edu.kh.project.board.model.dto.Comment;
public interface CommentService {
/** 댓글 목록 조회
* @param boardNo
* @return List
*/
List<Comment> select(int boardNo);
/** 댓글 삽입
* @param comment
* @return result
*/
int insert(Comment comment);
/** 댓글 삭제
* @param commentNo
* @return result
*/
int delete(int commentNo);
/** 댓글 수정
* @param comment
* @return result
*/
int update(Comment comment);
}
package edu.kh.project.board.model.service;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import edu.kh.project.board.model.dao.CommentDAO;
import edu.kh.project.board.model.dto.Comment;
import edu.kh.project.common.utility.Util;
@Service
public class CommentServiceImpl implements CommentService{
@Autowired
private CommentDAO dao;
// 댓글 목록 조회
@Override
public List<Comment> select(int boardNo) {
return dao.select(boardNo);
}
// 댓글 삽입
@Transactional(rollbackFor = Exception.class)
@Override
public int insert(Comment comment) {
// XSS 방지 처리
comment.setCommentContent( Util.XSSHandling(comment.getCommentContent()) );
return dao.insert(comment);
}
// 댓글 삭제
@Transactional(rollbackFor = Exception.class)
@Override
public int delete(int commentNo) {
return dao.delete(commentNo);
}
// 댓글 수정
@Transactional(rollbackFor = Exception.class)
@Override
public int update(Comment comment) {
// XSS 방지 처리
comment.setCommentContent( Util.XSSHandling(comment.getCommentContent()) );
return dao.update(comment);
}
}
package edu.kh.project.board.model.dao;
import java.util.List;
import java.util.Map;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import edu.kh.project.board.model.dto.Comment;
@Repository
public class CommentDAO {
@Autowired
private SqlSessionTemplate sqlSession;
/** 댓글 목록 조회
* @param boardNo
* @return List
*/
public List<Comment> select(int boardNo) {
return sqlSession.selectList("boardMapper.selectCommentList", boardNo);
}
/** 댓글 삽입
* @param comment
* @return result
*/
public int insert(Comment comment) {
return sqlSession.insert("commentMapper.insert", comment);
}
/** 댓글 삭제
* @param commentNo
* @return result
*/
public int delete(int commentNo) {
return sqlSession.update("commentMapper.delete", commentNo);
}
/** 댓글 수정
* @param comment
* @return result
*/
public int update(Comment comment) {
return sqlSession.update("commentMapper.update", comment);
}
}
<?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="commentMapper">
<!-- 댓글 삽입 -->
<insert id="insert">
INSERT INTO "COMMENT"
VALUES(SEQ_COMMENT_NO.NEXTVAL,
#{commentContent},
DEFAULT, DEFAULT,
#{boardNo}, #{memberNo},
<!-- 동적 sql : if문 -->
<!-- 부모 댓글 -->
<if test="parentNo == 0"> NULL </if>
<!-- 자식 댓글 -->
<if test="parentNo != 0"> #{parentNo} </if>
)
</insert>
<!-- 댓글 삭제 -->
<update id="delete">
UPDATE "COMMENT" SET
COMMENT_DEL_FL = 'Y'
WHERE COMMENT_NO = #{commentNo}
</update>
<!-- 댓글 수정 -->
<update id="update">
UPDATE "COMMENT" SET
COMMENT_CONTENT = #{commentContent}
WHERE COMMENT_NO = #{commentNo}
</update>
</mapper>
댓,답글 수정 및 삭제 가능