고래씌

[Spring] 9. 댓글 목록, 등록, 수정, 삭제(비동기식) 본문

Server/Spring

[Spring] 9. 댓글 목록, 등록, 수정, 삭제(비동기식)

고래씌 2024. 1. 26. 16:12

1. 댓글 목록

 
=> 다음과 같이 파일 모두 생성

 
 
 
▶ Reply.vo

package com.kh.spring.board.model.vo;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@NoArgsConstructor
@ToString
public class Reply {
	private int replyNo;
	private String replyContent;
	private String refBno;
	private String replyWriter;
	private String createDate;
	private String status;
}

 
 
▶ ReplyController.java

@RestController

 
Rest(Representaional state Transfer) : 자원을 url 이름으로 구분하여 자원의 상태(state)를 주고받는것(transfer)
=> @ResponseBody + @Controller : 요청에 따른 응답데이터가 모두 값(JSON) 그 자체인 컨트롤러
=> 비동기방식으로 통신하는 메소드들로만 몸통이 구현되어 있음
 

package com.kh.spring.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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.kh.spring.board.model.service.ReplyService;
import com.kh.spring.board.model.vo.Reply;

@RestController
@RequestMapping("/reply")
public class ReplyController {
	
	@Autowired
	private ReplyService service;
	
	// 댓글목록 조회(select) 서비스 → GET
	@GetMapping("/selectReplyList")
	public List<Reply> selectReplyList(int boardNo) {
		List<Reply> rList = service.selectReplyList(boardNo);  // 알아서 JSON 형태로 반환해줌
		
		return rList;
	}
	
	
}

 
 
▶ ReplyService.java

package com.kh.spring.board.model.service;

import java.util.List;

import com.kh.spring.board.model.vo.Reply;

public interface ReplyService {

//	댓글 목록 조회
	List<Reply> selectReplyList(int boardNo);

}

 
 
▶ ReplyServiceImpl.java

package com.kh.spring.board.model.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.kh.spring.board.model.dao.ReplyDao;
import com.kh.spring.board.model.vo.Reply;

@Service
public class ReplyServiceImpl implements ReplyService{
	
	@Autowired
	private ReplyDao replyDao;

	@Override
	public List<Reply> selectReplyList(int boardNo) {
		return replyDao.selectReplyList(boardNo);
	}
}

 
 
▶ ReplyDao.java

package com.kh.spring.board.model.dao;

import java.util.List;

import com.kh.spring.board.model.vo.Reply;

public interface ReplyDao {

//	댓글 목록 조회
	List<Reply> selectReplyList(int boardNo);

}

 
 
▶ ReplyDaoImpl.java

package com.kh.spring.board.model.dao;

import java.util.List;

import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.kh.spring.board.model.vo.Reply;

@Repository
public class ReplyDaoImpl implements ReplyDao{
	
	@Autowired
	private SqlSessionTemplate sqlSession;

//	댓글목록 조회
	@Override
	public List<Reply> selectReplyList(int boardNo) {
		return sqlSession.selectList("replyMapper.selectReplyList", boardNo);
	}
}

 
 
▶ reply-mapper.xml

<?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="replyMapper">
	
	<select id="selectReplyList" parameterType="int" resultType="reply">
		SELECT
			REPLY_NO,
			REPLY_CONTENT,
			REF_BNO,
			USER_NAME AS REPLY_WRITER,
			CREATE_DATE,
			REPLY.STATUS
		FROM REPLY
		LEFT JOIN MEMBER ON REPLY_WRITER = USER_NO
		WHERE REF_BNO = #{boardNo} AND REPLY.STATUS = 'Y'
	</select>
	
</mapper>

 
 
▶ boardDetailView.jsp
맨아래에 아래 코드 ajax 코드 추가

	<script>
		// 댓글목록 비동기 조회
		function selectReplyList(){
			$.ajax({
				url : "${contextPath}/reply/selectReplyList",
				data : {
					boardNo : '${boardNo}'
				},
				success : function(result){  // result → List<Reply> → [ {댓글}, {댓글} ] 
					let replys = "";
					for(let reply of result){
						replys += "<tr>";
							replys += "<td>"+ reply.replyWriter +"</td>";
							replys += "<td>"+ reply.replyContent +"</td>";
							replys += "<td>"+ reply.createDate +"</td>";
						replys += "</tr>";
					}
					
					$("#replyArea tbody").html(replys);  // 문자열로 된 tr태그들을 다 넣어줌
					$("#rcount").html(result.length);  // 댓글 총 개수
				}
			})
		}
		
		selectReplyList(); // 이 페이지에 접근했을 때 이 함수가 호출되도록 설정
		
	</script>

 
 
▶ 결과

 
 
 


2. 댓글 등록

▶ boardDetailView.jsp

		// 댓글 추가
		function insertReply(){
			// Reply에 한행의 데이터를 기록
			
			$.ajax({
				url : '${contextPath}/reply/insertReply',
				data : {   // 백엔드서버에서 커맨드객체형태로 데이터를 얻어오기 위해서 Reply.vo에 객체이름을 똑같이 지정한다.
					refBno : '${boardNo}' 
					replyWriter : '${loginUser.userNo}',
					replyContent : $("#replyContent").val()
				},
				type : 'POST',
				success : function(result){
					if(result == 0){
						alert("댓글등록실패")
					}else{
						alert("댓글등록성공")
					}
					selectReplyList();  // 댓글 목록 불러오는 함수
					$("#replyContent").val("");
				}
			})
		}

 
 
▶ ReplyController.java

	// 댓글 등록
	@PostMapping("/insertReply")
	public int insertReply(Reply r) {
		return service.insertReply(r);
	}

 
 
▶ ReplyService.java

	int insertReply(Reply r);

 
 
▶ ReplyServiceImpl.java

	@Override
	public int insertReply(Reply r) {
		return replyDao.insertReply(r);
	}

 
 
 ▶ ReplyDao.java

	int insertReply(Reply r);

 
 
▶ ReplyDaoImpl.java

	@Override
	public int insertReply(Reply r) {
		return sqlSession.insert("replyMapper.insertReply",r);
	}

 
 
▶ reply-mapper.xml

	<!-- 댓글 등록 -->	
	<!-- replyContent는 XSS 공격을 받기 되게 좋음 -->
	<insert id="insertReply" parameterType='reply'>
		INSERT INTO REPLY
		VALUES(SEQ_RNO.NEXTVAL, #{replyContent}, #{refBno}, #{replyWriter}, DEFAULT, DEFAULT)
	</insert>

 
 


3. 댓글 수정

mybatis를 사용할 때, parameterMap 및 parameterType, resultMap 및 resultType을 선언하여 사용한다.

간단하게 정리하면 다음과 같이 정리할 수 있다.

  • parameterMap : 비즈니스 로직으로부터 전달 받은, SQL 구문에 사용될 매개변수를 담은 객체
  • parameterType : 비즈니스 로직으로부터 전달 받은, SQL 구문에 사용될 매개변수의 자료형
  • resultMap : 비즈니스 로직으로 반환할, 결과값을 담은 객체
  • resultType : 비즈니스 로직으로 반환할, 결과값의 자료형

 
▶ boardDetailView.jsp

 
=> 댓글 목록을 불려왔었던 코드에서 수정/삭제할 수 있도록 버튼 추가
 

		function showReplyUpdateForm(replyNo, btn){
			
			// 1. 댓글을 수정할 수 있는 textArea 생성
			const textarea = document.createElement("textarea");   // 수정하기 폼
			const button = document.createElement("button");   // 수정버튼
			button.innerText = "수정";
			
			// 버튼요소기준 부모요소(td)의 부모요소(tr)의 자식들([td, td, td]을 1번 인덱스에 있는 자식.(댓글내용 td)) 
			let td = btn.parentElement.parentElement.children[1];    // 댓글내용이 있는 td태그
			
			textarea.innerHTML = td.innerHTML; // 댓글내용복사
			
			td.innerHTML = "";
			td.append(textarea);
			td.append(button);
			
			button.onclick = () => updateReply(replyNo, textarea);

			
		}

 
 
=> 자바스크립트 객체를 JSON 형식으로 보내야한다!!!

$.ajax({
   url : '${contextPath}/reply/update/'+replyNo,
   data : JSON.stringify(reply),    // JSON 문자열 형태로 변환시켜서 전달
   type : 'PUT',
   contentType : 'application/json;charset=UTF-8',
   success : function(result){
      if(result > 0){
         alert("수정성공")
         selectReplyList();
     }else{
        alert("수정실패")
     }
   }
})
		// 자바스크립트 객체를 JSON 형식으로 보내야함
		function updateReply(replyNo, textarea){
			let reply = {
					replyNo,
					replyContent : textarea.value
			};
			
			$.ajax({
				url : '${contextPath}/reply/update/'+replyNo,
				data : JSON.stringify(reply),    // JSON 문자열 형태로 변환시켜서 전달
				type : 'PUT',
				contentType : 'application/json;charset=UTF-8',
				success : function(result){
					if(result > 0){
						alert("수정성공")
						selectReplyList();
					}else{
						alert("수정실패")
					}
				}
			})
		}

 
 
 
▶ ReplyController.java
=> 댓글 내용이 보이게 하려면 RequestBody를 추가하여야함 
=> 댓글 등록 및 수정에서도 XSS핸들링, 개행처리 해줘야한다!! XSS 공격을 막기위해서 현재는 그냥 진행

	@PutMapping("/update/{replyNo}") // url로 전달받은 자원을 PathVariable 이용해서 뽑아옴
	public int updateReply(@PathVariable("replyNo") int replyNo,
			@RequestBody Reply r) {
		
		log.info("reply ? {}", r);
		
		return service.updateReply(r);
	}

 
 
▶ ReplyService.java

	int updateReply(Reply r);

 
 
▶ ReplyServiceImpl.java

	@Override
	public int updateReply(Reply r) {
		return replyDao.updateReply(r);
	}

 
 
▶ ReplyDao.java

	int updateReply(Reply r);

 
 
▶ ReplyDaoImpl.java

	@Override
	public int updateReply(Reply r) {
		return sqlSession.update("replyMapper.updateReply", r);
	}

 
 
▶ reply-mapper.xml

	<!-- 댓글 수정 -->
	<update id="updateReply" parameterType="reply" >
		UPDATE REPLY
		SET REPLY_CONTENT = #{replyContent}
		WHERE REPLY_NO = #{replyNo}
	</update>

 
 


4. 댓글 삭제

▶ boardDetailVeiw.jsp

		function deleteReply(replyNo){
			if(confirm('정말로 댓글을 삭제하겠습니까?')) {
				
				$.ajax({
					url : '${contextPath}/reply/delete/'+replyNo,
					data : {replyNo},
					type : 'delete',
					success : function(result){
						if(result > 0){
							alert("삭제성공")
							selectReplyList();
						}else{
							alert("삭제실패")
						}
					}
				})
			}
		}

 
 
▶ ReplyController.java

//	@PostMapping("/delete")
	@DeleteMapping("/delete/{replyNo}")
	public int deleteReply(@PathVariable("replyNo") int replyNo) {
		return service.deleteReply(replyNo);
	}

 
 
▶ ReplyService.java

	int deleteReply(int replyNo);

 
▶ ReplyServiceImpl.java

	@Override
	public int deleteReply(int replyNo) {
		return replyDao.deleteReply(replyNo);
	}

 
 
▶ ReplyDao.java

	int deleteReply(int replyNo);

 
 
▶ ReplyDaoImpl.java

	@Override
	public int deleteReply(int replyNo) {
		return sqlSession.delete("replyMapper.deleteReply", replyNo);
	}

 
 
▶ reply-mapper.xml

	<delete id="deleteReply" parameterType='int'>
		DELETE FROM REPLY
		WHERE REPLY_NO = #{replyNo}
	</delete>

 
 
▶ 결과