Spring AJAX ,@ResponseBody로 댓글달기

2022. 4. 15. 13:26Spring/Spring 실습

댓글 개요

댓글은 freeView에만 추가할 예정입니다.

댓글기능을 추가하기전 naver웹툰에 댓글을 참고합시다.

 

URL은 변화가 없는데 댓글부분의 보이는 화면이 다르다.

AJAX로 데이터를 변경했기 때문이다. 

우리는 Spring 프로젝트 진행 중 freeView화면에서 AJAX로 요청을 하고

Controller에서 DB에 접근해서 댓글데이터를 얻을 것이다.

그리고  ReplyVO(댓글번호, 댓글작성자,댓글내용 등)에 저장할 것이다.

@ResponseBody를 붙인 메소드에서 return한 값은

그대로 AJAX succes함수의 파라미터로 온다.

이 ReplyVO를 AJAX요청한 곳에 return 하고

ReplyVO를 자바스크립트로 댓글형태의 태그를 생성한다. 

 

 

Controller

//예시
@ResponseBody
@RequestMapping(value="/reply/replyList.wow")
public List<ReplyVO> replyList(){
	List<ReplyVO> replyList=getReplyList();
    return replyList;
}

 

 

script

//예시
$.ajax({
        url : "<c:url value='replyList.wow'>"
        ,type : "post"
        ,success : function(data){

        }
    });//ajax

문제는 javascript에는 List<ReplyVO>라는 자료타입이 없다는 것이다.

https://brilliantdevelop.tistory.com/100에서 처럼

Spring은 return값을 response의 응답몸체로 변환해주어야 한다.

int,String등은 기본적으로 HttpMessageConverter 이용해서 응답몸체로 변환해주지만

List<ReplyVO>라는 객체를 바로 응답몸체로 변환 할 수 없다. 

Spring은 자바객체를 반드시 적절한 형태로 바꾼 후에  응답몸체로 변환한다. 

이 적절한 형태는 xml,csv,json 등이 있다. 

이 글에서는 json 형태로 바꿔서 응답몸체로 변환하려한다. 

이를 위해 jackson라이브러리를 추가하자.

<!-- json변환 -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.12.3</version>
</dependency>

jackson-databind 디펜던시를 추가하면  Spring의  MappingJackson2HttpMessageConver

jackson lib를 이용해서 자바객체와 JSON간의 변환을 처리한다.

 

 

 

 

 

댓글 적용하기

댓글테이블

댓글 테이블을 만들자.

CREATE SEQUENCE seq_reply;

CREATE TABLE reply(
  re_no         NUMBER NOT NULL,
  re_category   VARCHAR2(30) NOT NULL,
  re_parent_no  NUMBER NOT NULL,
  re_mem_id     VARCHAR2(30) NOT NULL,
  re_content    VARCHAR2(4000) ,
  re_ip  VARCHAR2(30),
  re_reg_date DATE DEFAULT SYSDATE, 
  re_mod_date DATE, 
  CONSTRAINT pk_reply PRIMARY KEY (re_no)  
);
COMMENT ON table reply is  '댓글정보 테이블';
COMMENT ON COLUMN reply.re_no IS  '댓글번호';
COMMENT ON COLUMN reply.re_category IS '분류(BOARD, PDS, FREE, ...)';
COMMENT ON COLUMN reply.re_parent_no IS '부모 번호';
COMMENT ON COLUMN reply.re_mem_id   IS '작성자ID';
COMMENT ON COLUMN reply.re_content  IS '댓글 내용';
COMMENT ON COLUMN reply.re_ip       IS 'IP';
COMMENT ON COLUMN reply.re_reg_date IS '댓글 등록일자';
COMMENT ON COLUMN reply.re_mod_date IS '댓글 수정일자';

re_no 컬럼은 primaryKey로서 댓글한개의 고유번호이다.

중요한건 re_parent_no와 re_category이다.

 freeView 글 1개에 댓글이 여러개 일 수 있다.   

그래서 free_board테이블과  reply테이블은 1:n 관계이다.

이를 위해 reply테이블에 re_parent_no컬럼이 있다.

또 아직 만들지는 않았지만 만약에 공지사항 게시판을 만들고

거기에 댓글을 여러개 달 수 있는 경우를 생각해보자.

공지사항과 reply테이블은 1:n 관계이다.

그런데 reply테이블입장에서 re_parent_no=1인 경우를 보면

공지사항 1번글의 댓글인지, freeBoard 1번글의 댓글인지 모른다

이를 구분하기 위해 re_category 컬럼이 있다.

(물론 댓글테이블에 공지사항댓글,freeBoard댓글을 다 넣어서 이를 구별하기 위해

re_category 컬럼이 있지만 애초에 공지사항댓글테이블 reply_anounce,  free_board댓글 테이블 reply_free_board를 따로 만들 수도 있다.)

 

 

 

 

ReplyVO

ReplyVO를 만듭시다.

package com.study.reply.vo;
import java.io.Serializable;

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
public class ReplyVO { 
	private int reNo; /* 댓글번호 */
	private String reCategory; /* 분류(BOARD, PDS, FREE, ...) */
	private int reParentNo; /* 부모 번호 */
	private String reMemId; /* 작성자ID */
	private String reContent; /* 댓글 내용 */
	private String reIp; /* IP */
	private String reRegDate; /* 댓글 등록일자 */
	private String reModDate; /* 댓글 수정일자 */
	private String reMemName; //이름
	// toString() 구현
	@Override
	public String toString() {
		return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
	}

	//get/set
}

 

 

 

 

 

댓글부분 html태그

freeView.jsp에 다음의 댓글 html태그를 추가합시다.

<div class="container"> 로 시작하는 댓글목록 태그이므로

기존의 있던 <div class="container">태그의 밑에다 추가하면 되겠습니다

freeView.jsp

<!-- 기존  freeView태그
<div class="container">
</div>
-->
<div class="container">
		<!-- reply container -->
		<!-- // START : 댓글 등록 영역  -->
		<div class="panel panel-default">
			<div class="panel-body form-horizontal">
				<form name="frm_reply" action="<c:url value='/reply/replyRegist' />"
					method="post" onclick="return false;">
					<input type="hidden" name="reParentNo" value="${freeBoard.boNo}">
					<input type="hidden" name="reCategory" value="FREE"> <input
						type="hidden" name="reMemId" value="${USER_INFO.userId }">  
					<input type="hidden" name="reIp"
						value="<%=request.getRemoteAddr()%>">
					<div class="form-group">
						<label class="col-sm-2  control-label">댓글</label>
						<div class="col-sm-8">
							<textarea rows="3" name="reContent" class="form-control" 
							readonly='readonly'></textarea>
						</div>
						<div class="col-sm-2">
							<button id="btn_reply_regist" type="button"
								class="btn btn-sm btn-info">등록</button>
						</div>
					</div>
				</form>
			</div>
		</div>
		<!-- // END : 댓글 등록 영역  -->


		<!-- // START : 댓글 목록 영역  -->
		<div id="id_reply_list_area">
			<div class="row">
				<div class="col-sm-2 text-right">홍길동</div>
				<div class="col-sm-6">
					<pre>내용</pre>
				</div>
				<div class="col-sm-2">12/30 23:45</div>
				<div class="col-sm-2">
					<button name="btn_reply_edit" type="button"
						class=" btn btn-sm btn-info" onclick="fn_modify()">수정</button>
					<button name="btn_reply_delete" type="button"
						class="btn btn-sm btn-danger">삭제</button>
				</div>
			</div>
			<div class="row">
				<div class="col-sm-2 text-right">그댄 먼곳만 보네요</div>
				<div class="col-sm-6">
					<pre> 롤링롤링롤링롤링</pre>
				</div>
				<div class="col-sm-2">11/25 12:45</div>
				<div class="col-sm-2"></div>
			</div>
		</div>

		<div class="row text-center" id="id_reply_list_more">
			<a id="btn_reply_list_more"
				class="btn btn-sm btn-default col-sm-10 col-sm-offset-1"> <span
				class="glyphicon glyphicon-chevron-down" aria-hidden="true"></span>
				더보기
			</a>
		</div>
		<!-- // END : 댓글 목록 영역  -->


		<!-- START : 댓글 수정용 Modal -->
		<div class="modal fade" id="id_reply_edit_modal" role="dialog">
			<div class="modal-dialog">
				<!-- Modal content-->
				<div class="modal-content">
					<form name="frm_reply_edit"
						action="<c:url value='/reply/replyModify' />" method="post"
						onclick="return false;">
						<div class="modal-header">
							<button type="button" class="close" data-dismiss="modal">×</button>
							<h4 class="modal-title">댓글수정</h4>
						</div>
						<div class="modal-body">
							<input type="hidden" name="reNo" value="">
							<textarea rows="3" name="reContent" class="form-control"></textarea>
							<input type="hidden" name="reMemId" value="${USER_INFO.userId }">
						</div>
						<div class="modal-footer">
							<button id="btn_reply_modify" type="button"
								class="btn btn-sm btn-info">저장</button>
							<button type="button" class="btn btn-default btn-sm"
								data-dismiss="modal">닫기</button>
						</div>
					</form>
				</div>
			</div>
		</div>
		<!-- END : 댓글 수정용 Modal -->

	</div>
	<!-- reply container -->

<!-- 이 다음이 </body>태그 -->

 

 

 

 

 

freeView페이지를 보면  다음과 같이 나옵니다.

 

 

먼저 등록부분을 readonly로 했는데, 로그인한 사람만 댓글을 달 수 있도록 하려고한다.

마찬가지로 수정,삭제도 로그인한 사람만 달 수 있도록 하려고한다. 

2개의 댓글예시는 하드코딩했지만, 나중엔 DB에 있는 댓글에 따라 보이는게 달라질 것이다.

첫번째 댓글의 수정,삭제버튼은 내가 그 글을 작성한 사람일 때만 보인다.

두번째 댓글은 로그인 안 했거나 내가 쓴 글이 아니어서 버튼이 보이지 않느다.

또 더보기 버튼을 누르면 댓글이 10개 씩 더 보이도록 할 것이다.

 

 

 

 

LoginCheckInterceptor

로그인한 사람만 등록,수정,삭제를 할 수 있도록 LoginCheckInterceptor를 수정하고

,mvc-servlet.xml에 mapping을 등록하자.

package com.study.common.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import com.study.login.vo.UserVO;
public class LoginCheckInterceptor extends HandlerInterceptorAdapter {
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		String ajax= request.getHeader("X-requested-with"); //요청이 ajax인지아닌지 
		HttpSession session=request.getSession();   
		UserVO user=(UserVO)session.getAttribute("USER_INFO");
		if(user==null) {
        	//추가된부분
			if(ajax!=null) { //ajax요청일 경우
				response.sendError(401, "로그인안했어요"); //ajax error함수에서 login으로 이동하게 할거임.
				return false;
			}
			
			response.sendRedirect(request.getContextPath()+"/login/login.wow");
			return false;
		}
		return true;
	}
}

요청을 ajax로 하기 때문에 response.sendRedirect()로 로그인페이지로 가는것이 아닌,

ajax로 error를 보낸 다음 ajax의 error함수에서 login페이지로 이동하도록 할 것이다.

 

 

 

mvc-servlet.xml

<interceptors>
    <interceptor>
        <mapping path="/mypage/*" /> <!-- mapping은 특정 url요청이 올때 무언가 하겠다 -->
        <mapping path="/reply/*" />  <!--reply관련요청 인터셉트-->
        <exclude-mapping path="/reply/replyList.wow"/>
        <beans:bean
            class="com.study.common.interceptor.LoginCheckInterceptor"></beans:bean>   <!-- 무언가에 대한 내용 -->
    </interceptor>
</interceptors>

 

 

 

 

 

이제 script에서 댓글관련 버튼을 누를 때마다 이벤트를 잘 등록해주기만 하면 된다.

sciript(ajax요청) , controller , service,      dao,mapper.xml만  작성하면 된다.

 

Script

<script type="text/javascript">
	// 댓글 데이터를 딱 10개만 가지고 오도록 하는 파라미터 모음
    var params={"curPage":1, "rowSizePerPage" : 10
            ,"reCategory" : "FREE", "reParentNo": ${freeBoard.boNo} };
            
	//ajax 요청해서 댓글리스트를 받아오는 함수.
    function fn_reply_list(){
		//아작스 호출해서 DB에 있는 reply 데이터 가지고 옵니다.
		//가지고오면(success)하면 댓글 div 만들어줍니다. 
		//list를 가지고오니까 jquery 반복문 써서 div 여러개 만들어주면됩니다.
        // 다 했으면 param의 curPage=2로 바꿔줍시다
    }//function fn_reply_list

    $(document).ready(function(){ //documnet가 준비될 때 
        //더보기 버튼
        $("#id_reply_list_more").on("click",function(e){
			//fn_reply_list에서 마지막에 curPage=2로 바꿔줍니다. 
            //그래서 그냥 fn_reply_list()하면 다음 댓글 10개 가져옵니다.
		});

        //등록버튼
        $("#btn_reply_regist").on("click",function(e){
			// form태그안에 input hidden으로 필요한거 넣기
            //가장가까운 form찾은 후 ajax 호출(data는 form.serialize(), )
		    //성공 : 등록 글 내용부분 지우기,  댓글영역초기화( list_area.html('), curPage=1, fn_reply_list)
            //실패 : error : req.status==401이면 login으로   location.href
        });//등록버튼

        
      	//수정버튼 : 댓글 영역안에 있는 수정버튼만  이벤트 등록 
        $("#id_reply_list_area").on("click", 'button[name="btn_reply_edit"]'
                ,function(e){
           //현재 버튼의 상위 div(한개 댓글) 찾기
 	       //div에서 현재 댓글 내용을 modal에 있는 textarea에 복사
	       //div태그의 data-re-no 값을 modal에 있는 input name="reNo" 태그의 value값에 복사 
           //복사 후   .modal('show')
        });//수정버튼


        //모달창 저장 버튼
        $("#btn_reply_modify").on("click", function(e){
			//가장 가까운form 찾기 , ajax 호출(  data:form.serialzie()
            // 성공 :  modal찾은 후 modal('hide')
            // 현재 모달에 있는 reNo, reContent 찾기
            // 댓글영역에서 re_no에 해당하는 댓글 찾은 후 안의 내용 re_content로 변경
        });//모달창 저장버튼


        //삭제버튼
        $("#id_reply_list_area").on("click", 'button[name="btn_reply_delete"]'
                ,function(e){
			//가장 가까운 div 찾기, 
			//reNo,  reMemId(현재 로그인 한 사람의 id) 구하기
            // ajax 호출(reNo, reMemeId보내기) reMemId는 본인이 쓴 글인지 확인하는데 쓰임 (BizAccessFailException)
	        //성공  후 해당 div.remove 
        }); //삭제버튼


    });
</script>

 

 

 

 

Controller

서버단에서는 메소드이름만 보면 어떤식으로 구현할지 감이 올것이다.

ReplyController

package com.study.reply.web;

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

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.study.exception.BizNotFoundException;
import com.study.reply.vo.ReplySearchVO;
import com.study.reply.vo.ReplyVO;

@RestController
public class ReplyController {
@RequestMapping(value = "/reply/replyList.wow")
	public Map<String,Object> replyList(PagingVO paging,String reCategory,int reParentNo){
		return null;
	}
	
	@RequestMapping(value = "/reply/replyRegist.wow")
	public Map<String,Object> replyRegist(ReplyVO reply){
		//result, msg  (msg는 "등록성공", "등록실패"
		return null;
	}
	
	@RequestMapping(value = "/reply/replyModify.wow")
	public Map<String,Object> replyModify(ReplyVO reply){
		//result, msg
		return null;
	}
	
	@RequestMapping(value = "/reply/replyDelete.wow")
	public Map<String,Object> replyDelete(ReplyVO reply){
		//result, msg
		return null;
	}

}

 

 

 

Service

IReplyService

package com.study.reply.service;

import java.util.List;

import com.study.exception.BizAccessFailException;
import com.study.exception.BizNotFoundException;
import com.study.reply.vo.ReplySearchVO;
import com.study.reply.vo.ReplyVO;

public interface IReplyService {
/** 댓글 목록 조회 <br>
* <b>필수 : reCategory, reParentNo </b>
*/
public List<ReplyVO> getReplyListByParent(PagingVO paging, String reCategory, int reParentNo);
/** 댓글 수정 <br>
* 댓글이 존재하지 않으면 BizNotFoundException
* 댓글 작성자와 로그인 사용자가 다른 경우 BizAccessFailException
*/
public void modifyReply(ReplyVO reply) throws BizNotFoundException, BizAccessFailException;
/**
* 댓글 삭제 <br>
* 해당글의 존재하지 않으면 BizNotFoundException
* 댓글 작성자와 로그인 사용자가 다른 경우 BizAccessFailException
*/
public void removeReply(ReplyVO reply) throws BizNotFoundException, BizAccessFailException;
/** 댓글등록 */
public void registReply(ReplyVO reply) ;


}

 

 

또 혹시로그인을 했어도 현재 로그인ID가 수정할려는 댓글의 로그인ID랑 다를 때 수정이 안되도록 BizAccessFailException을 발생하려고 한다. 

BizAccessFailException.java

package com.study.exception;

public class BizAccessFailException extends BizException {

	public BizAccessFailException() {
	}

	public BizAccessFailException(String message) {
		super(message);
	}

	public BizAccessFailException(Throwable cause) {
		super(cause);
	}

	public BizAccessFailException(String message, Throwable cause) {
		super(message, cause);
	}

	public BizAccessFailException(String message, Throwable cause, boolean enableSuppression,
			boolean writableStackTrace) {
		super(message, cause, enableSuppression, writableStackTrace);
	}

}

 

 

 

DAO

IReplyDao.java

package com.study.reply.dao;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;

import com.study.reply.vo.ReplySearchVO;
import com.study.reply.vo.ReplyVO;

@Mapper
public interface IReplyDao {
public int getReplyCountByParent(@Param("paging") PagingVO paging, @Param("reCategory") String reCategory,@Param("reParentNo") int reParentNo);

	public List<ReplyVO> getReplyListByParent(@Param("paging")PagingVO paging, @Param("reCategory") String reCategory,@Param("reParentNo") int reParentNo);

	public ReplyVO getReply(int reNo);

	public int updateReply(ReplyVO reply);

	public int deleteReply(ReplyVO reply);

	public int insertReply(ReplyVO reply);
}

 

 

 

 

 

완성

script:

<script type="text/javascript">
var params={"curPage":1, "rowSizePerPage" : 10
		,"reCategory" : "FREE", "reParentNo": ${freeBoard.boNo} };
	
function fn_reply_list(){
	//아작스 호출해서 DB에 있는 reply 데이터 가지고 옵니다.
	//가지고오면(success)하면 댓글 div 만들어줍니다. 
	//list를 가지고오니까 jquery 반복문 써서 div 여러개 만들어주면되겠죠?
	$.ajax({
		url : "<c:url value='/reply/replyList.wow' />"
		,type: "POST"
		,data : params
		,dataType: 'JSON'     //받을 때 data를 어떻게 받을지  
		, success: function(data){
			console.log(data);
			$.each(data.data, function(index, element) {
				var str="";
				 str=str+'<div class="row" data-re-no="'+ element.reNo +'">'
			        +'<div class="col-sm-2 text-right" >'+element.reMemName+ '</div>'
			        +'<div class="col-sm-6"><pre>'+element.reContent+ '</pre></div>'
			        +'<div class="col-sm-2" >'+element.reRegDate +'</div>'
			        +'<div class="col-sm-2">';	
			        if(element.reMemId=="${USER_INFO.userId}"){		
			          str=str+   '<button name="btn_reply_edit" type="button" class=" btn btn-sm btn-info" >수정</button>'
			                 +  '<button name="btn_reply_delete" type="button" class="btn btn-sm btn-danger" >삭제</button>';
			        	}
			       str=str+'</div>'
			            +'</div>';
			       $('#id_reply_list_area').append(str);
			});
			       params.curPage+=1;
		}//success
	});	//ajax
}//function fn_reply_list

$(document).ready(function(){ //documnet가 준비될 때 
	fn_reply_list();  //freeView처음에 댓글 10개 보여주기
	// 등록버튼,     수정,삭제버튼,  모달의 등록버튼
	//더보기 버튼
	$("#id_reply_list_more").on("click",function(e){
		e.preventDefault();
		fn_reply_list();
	});
	
	//등록버튼
	$("#btn_reply_regist").on("click",function(e){
		e.preventDefault();
		$form=$(this).closest("form[name='frm_reply']");
		$.ajax({
			url:"<c:url value='/reply/replyRegist.wow'/>"
			,type : "POST"
			,dataType :"JSON"
			,data : $form.serialize()
			,success: function(data){
				console.log(data);
				$form.find("textarea[name='reContent']").val('');
				$("#id_reply_list_area").html('');
				params.curPage=1;
				fn_reply_list();
			}
			,error : function(req,st,err){
				if(req.status==401){
					location.href="<c:url value='/login/login.wow'  />";				
				}
			}
		});//ajax 
	});//등록버튼
	
	//수정버튼  function(){}은 동적으로 생긴 태그에도 적용이 되는거같아.. 
	//$().on("click") 동적으로생긴 태그에 적용됨
	$("#id_reply_list_area").on("click", 'button[name="btn_reply_edit"]'
			,function(e){
		//modal 아이디=id_reply_edit_modal
		//현재 버튼의  상위 div(하나의 댓글 전체)를 찾으세요
		// 그 div에서 현재 댓글의 내용 =modal에 있는 textarea에 복사 
		// 그 div태그의 data-re-no에 있는 값   $().data('re-no')
		//=modal에 있는  < input name=reNo>태그에 값으로 복사  
		//2개 복사했으면   $('#id_reply_edit_modal').modal('show')
		$btn=$(this);  //수정버튼
		$div=$btn.closest('div.row');   //버튼의 댓글 div
		$modal=$('#id_reply_edit_modal'); //modal div 
		$pre=$div.find('pre'); 
		 var content=$pre.html(); 
		 $textarea=$modal.find('textarea'); 
		
		 $textarea.val(content);  
		 var reNo=$div.data('re-no');	
		 $modal.find('input[name=reNo]').val(reNo);
		 $modal.modal('show');
	});//수정버튼
	

	//모달창 저장 버튼
	$("#btn_reply_modify").on("click", function(e){
		e.preventDefault(); 
		$form= $(this).closest('form[name="frm_reply_edit"]');
		$.ajax({
			url : "<c:url value='/reply/replyModify.wow' />"
			,type : "POST"
			,data : $form.serialize()
			,dataType : "JSON"
			,success: function(){
				$modal=$('#id_reply_edit_modal'); 
				$modal.modal('hide');
				
				var reNo=$modal.find('input[name=reNo]').val();
				var reContent=$modal.find('textarea').val();
				$("#id_reply_list_area").find("div[data-re-no='"+reNo+"']").find("pre").html(reContent);
			}
		});//ajax
	});//모달창 저장버튼
	
	
	//삭제버튼
	$("#id_reply_list_area").on("click", 'button[name="btn_reply_delete"]'
			,function(e){
		e.preventDefault();
		$div=$(this).closest('.row');
		reNo=$div.data('re-no');
		reMemId="${USER_INFO.userId}";
		$.ajax({
			url : "<c:url value='/reply/replyDelete.wow' />"
			,type : "POST"
			,data : {"reNo" : reNo, "reMemId" : reMemId}
			,dataType : 'JSON'
			,success : function(){
				$div.remove();
			}
		});//ajax
	}); //삭제버튼
	
	
});

</script>

 

 

 

 

ReplyController.java

package com.study.reply.web;

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

import javax.inject.Inject;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.study.exception.BizAccessFailException;
import com.study.exception.BizNotFoundException;
import com.study.reply.service.IReplyService;
import com.study.reply.vo.ReplySearchVO;
import com.study.reply.vo.ReplyVO;

@RestController
public class ReplyController {
	
	@Inject
	IReplyService replyService;
	
	@RequestMapping(value = "/reply/replyList.wow")
	public Map<String,Object> replyList(PagingVO paging,String reCategory,int reParentNo){
		List<ReplyVO> replyList=replyService.getReplyListByParent(paging,reCategory, reParentNo);
		Map<String,Object> map=new HashMap<String, Object>();
		map.put("result", true);
		map.put("data", replyList);
		map.put("size", replyList.size());
		return map;
	}
	
	@RequestMapping(value = "/reply/replyRegist.wow")
	public Map<String,Object> replyRegist(ReplyVO reply){
		System.out.println(reply);
		replyService.registReply(reply);
		
		Map<String,Object> map=new HashMap<String, Object>();
		map.put("result", true);
		map.put("msg", "등록성공했어요");
		return map;
	}
	
	@RequestMapping(value = "/reply/replyModify.wow")
	public Map<String,Object> replyModify(ReplyVO reply){
		Map<String, Object> map=new HashMap<String, Object>();
		try{
			replyService.modifyReply(reply);
			map.put("result", true);
			map.put("msg","수정성공");
		} catch (BizAccessFailException e) {
			map.put("result", false);
			map.put("msg","당신은 댓글을 쓴 사람이 아닙니다.");
		}catch (BizNotFoundException e) {
			map.put("result", false);
			map.put("msg","댓글이 없습니다.");
		}
		return map;
	}
	
	@RequestMapping(value = "/reply/replyDelete.wow")
	public Map<String,Object> replyDelete(ReplyVO reply){
		Map<String, Object> map=new HashMap<String, Object>();
		try{
			replyService.removeReply(reply);
			map.put("result", true);
			map.put("msg","삭제성공");
		} catch (BizAccessFailException e) {
			map.put("result", false);
			map.put("msg","당신은 댓글을 쓴 사람이 아닙니다.");
		}catch (BizNotFoundException e) {
			map.put("result", false);
			map.put("msg","댓글이 없습니다.");
		}
		return map;
	}
}

혹시 한글 인코딩에 문제가 있을 땐 https://brilliantdevelop.tistory.com/110를 참고해라.

 

 

 

 

ReplyServiceImpl.java

package com.study.reply.service;


import java.util.List;

import javax.inject.Inject;

import org.springframework.stereotype.Service;

import com.study.exception.BizAccessFailException;
import com.study.exception.BizNotFoundException;
import com.study.reply.dao.IReplyDao;
import com.study.reply.vo.ReplySearchVO;
import com.study.reply.vo.ReplyVO;

@Service
public class ReplyServiceImpl  implements IReplyService{
	@Inject
	IReplyDao replyDao;
	
	@Override
	public List<ReplyVO> getReplyListByParent(PagingVO paging, String reCategory, int reParentNo) {
		int totalRowCount=replyDao.getReplyCountByParent(paging,reCategory,reParentNo);
		paging.setTotalRowCount(totalRowCount);
		paging.pageSetting();
		List<ReplyVO> replyList=replyDao.getReplyListByParent(paging,reCategory,reParentNo);
		return replyList;
	}

	@Override
	public void modifyReply(ReplyVO reply) throws BizNotFoundException, BizAccessFailException {
		ReplyVO vo=replyDao.getReply(reply.getReNo()); //vo는 현재 DB에 있는 값   
		if(vo==null) throw new BizNotFoundException();
		if(!vo.getReMemId().equals(reply.getReMemId())) throw new BizAccessFailException();
		replyDao.updateReply(reply);
	}

	@Override
	public void removeReply(ReplyVO reply) throws BizNotFoundException, BizAccessFailException {
		ReplyVO vo=replyDao.getReply(reply.getReNo()); //vo는 현재 DB에 있는 값   
		if(vo==null) throw new BizNotFoundException();
		if(!vo.getReMemId().equals(reply.getReMemId())) throw new BizAccessFailException();
		replyDao.deleteReply(reply);
		
	}
	
	@Override
	public void registReply(ReplyVO reply) {
		replyDao.insertReply(reply);
	}
	
}

 

reply.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="com.study.reply.dao.IReplyDao">
	
	    <select id="getReplyCountByParent"
            resultType="int">
        SELECT count(*)
        FROM reply
        WHERE re_parent_no=#{reParentNo}
          AND re_category=#{reCategory}
    </select>

    <select id="getReplyListByParent"
            resultType="com.study.reply.vo.ReplyVO">
        SELECT *
        FROM
            (SELECT a.*,rownum  AS rnum
             FROM
                 (

        SELECT
        re_no, re_category, re_parent_no, re_mem_id
        , re_content, re_ip
        , to_char(re_reg_date,'YYYY-MM-DD') AS re_reg_date
        , to_char(re_mod_date,'YYYY-MM-DD') AS re_mod_date
        , b.mem_name AS re_mem_name
        FROM
        reply a, member b
        WHERE a.re_mem_id=b.mem_id
        AND re_parent_no=#{reParentNo}
        AND re_category=#{reCategory}
        ORDER by re_no desc
        )  a  ) b
        WHERE  rnum between    #{paging.firstRow} and #{paging.lastRow}

    </select>
	
	
	<insert id="insertReply" parameterType="com.study.reply.vo.ReplyVO">
		INSERT INTO reply (
		    re_no , re_category, re_parent_no
		    , re_mem_id, re_content, re_ip
		    , re_reg_date, re_mod_date
		) VALUES (
		   seq_reply.nextval, #{reCategory}, #{reParentNo}
		   ,#{reMemId},#{reContent}, #{reIp}
		   ,sysdate,null
		)
	</insert>
	
	
	<select id="getReply" resultType="com.study.reply.vo.ReplyVO" parameterType="int">
		SELECT
		    re_no , re_category, re_parent_no , re_mem_id
		    , re_content , re_ip , re_reg_date , re_mod_date
		FROM
		    reply
		WHERE re_no=#{reNo}
	</select>
	
	
	<update id="updateReply" parameterType="com.study.reply.vo.ReplyVO">
		UPDATE reply
		SET re_content=#{reContent}
		WHERE re_no=#{reNo}
	</update>
	
	
	<delete id="deleteReply" parameterType="com.study.reply.vo.ReplyVO">
		DELETE FROM reply 
		WHERE re_no=#{reNo}
	</delete>


</mapper>

 

 

 

여기까지했으면 댓글기능은 완성되었다고 볼 수 있다.

 

 

 

 

 

 

 

 

 

주의사항

근데 여기서 댓글자체와는 큰 관련이없지만, 아래의 경우를 보자.

 

 

 

이 화면에서 로그인을 아직 안했는데 등록 버튼을 누르면 로그인 화면으로 가게된다.

 

로그인을 성공하고 나면 무조건 홈화면으로 오게 된다.

 

 

 

 

 

이게 왜 그런거냐면 LoginController에서 로그인 성공했을 시

무조건 홈화면으로 redirect하도록 했기 때문이다.

 

 

LoginController의 

package com.study.login.web;

import java.net.URLEncoder;

import javax.inject.Inject;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.springframework.http.HttpRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import com.study.common.util.CookieUtils;
import com.study.login.service.ILoginService;
import com.study.login.service.LoginServiceImpl;
import com.study.login.vo.UserVO;

@Controller
public class LoginController {

	@Inject
	ILoginService loginService;
	
	
	@GetMapping("/login/login.wow")
	public String loginGet() {
		return "login/login";
	}

	@PostMapping("/login/login.wow")
	public String loginPost(HttpServletRequest req, HttpServletResponse resp) throws Exception {
		// 사용자가 id,pass입력해서 로그인버튼 누름
		String id = req.getParameter("userId");
		String pw = req.getParameter("userPass");
		String save_id = req.getParameter("rememberMe");
		if (save_id == null) {
			CookieUtils cookieUtils = new CookieUtils(req);
			if (cookieUtils.exists("SAVE_ID")) {
				Cookie cookie = CookieUtils.createCookie("SAVE_ID", id, "/", 0);
				resp.addCookie(cookie);
			}
			save_id = "";
		}

		if ((id == null || id.isEmpty()) || (pw == null || pw.isEmpty())) {
			return "redirect:" + "/login/login.wow?msg=" + URLEncoder.encode("입력안했어요", "utf-8");
		} else {
			UserVO user = loginService.getUser(id);
			if (user == null) {
				return "redirect:"  + "/login/login.wow?msg="
						+ URLEncoder.encode("아이디 또는 비번확인", "utf-8");
			} else { // id맞았을때
				if (user.getUserPass().equals(pw)) {// 다 맞는경우
					if (save_id.equals("Y")) {
						resp.addCookie(CookieUtils.createCookie("SAVE_ID", id, "/", 3600 * 24 * 7));
					}
					HttpSession session = req.getSession();
					session.setAttribute("USER_INFO", user);
					return "redirect:" + "/";
				} else {// 비번만 틀린경우
					return "redirect:" + "/login/login.wow?msg="
							+ URLEncoder.encode("아이디 또는 비번확인", "utf-8");
				}

			}
		}
	}
	
	@RequestMapping("/login/logout.wow")
	public String logout(	HttpSession session, HttpServletRequest req) {
				session.removeAttribute("USER_INFO");
				return "redirect:"+"/";  
	}
	
	
}

@PostMapping의  id,pw 다 맞는 경우를 보면

return "redirect:" +"/" 로  홈화면으로 redirect 하도록 되어있다.

그럼 어떻게 하면 freeView에서 로그인으로 이동했을 때

로그인성공하고나서 freeView로 다시 갈 수 있을까 ?

 

바로 request의 Header 중에 referer를 이용해 이전페이지 요청url을 얻을 수 있다.  

LoginController에  @GetMapping메소드에서 request의 referer가 freeView가 된다.

그래서 다음과 같이 prePage를 출력하면 

http://localhost:8080/study4/free/freeView.wow?boNo={글번호}를 얻을 수 있다.

@GetMapping("/login/login.wow")
public String loginGet(HttpServletRequest req) {
    String prePage=req.getHeader("referer");
    System.out.println(prePage);
    return "login/login";
}

 

 

저 prePage 값을 같은 Controller에 @PostMapping으로 넘겨주면 된다는 얘기이다. 

그러면 이제 로그인성공했을 시 바로 그 전 페이지로 이동할 수 있게 된다.

session을 통해  넘겨주면 다음과 같이 수정할 수 있다.

@GetMapping에서 session에 데이터 담고

@PostMapping의 다 맞는 경우 session에서 prePage를 얻어서 거기로 redirect했다.

@GetMapping("/login/login.wow")
public String loginGet(HttpServletRequest req,HttpSession session) {
    String prePage=req.getHeader("referer");
    session.setAttribute("PRE_PAGE",prePage );
    System.out.println(prePage);
    return "login/login";
}

@PostMapping("/login/login.wow")
public String loginPost(HttpServletRequest req, HttpServletResponse resp,HttpSession session) throws Exception {
    // 사용자가 id,pass입력해서 로그인버튼 누름
    String id = req.getParameter("userId");
    String pw = req.getParameter("userPass");
    String save_id = req.getParameter("rememberMe");
    if (save_id == null) {
        CookieUtils cookieUtils = new CookieUtils(req);
        if (cookieUtils.exists("SAVE_ID")) {
            Cookie cookie = CookieUtils.createCookie("SAVE_ID", id, "/", 0);
            resp.addCookie(cookie);
        }
        save_id = "";
    }

    if ((id == null || id.isEmpty()) || (pw == null || pw.isEmpty())) {
        return "redirect:" + "/login/login.wow?msg=" + URLEncoder.encode("입력안했어요", "utf-8");
    } else {
        UserVO user = loginService.getUser(id);
        if (user == null) {
            return "redirect:"  + "/login/login.wow?msg="
                    + URLEncoder.encode("아이디 또는 비번확인", "utf-8");
        } else { // id맞았을때
            if (user.getUserPass().equals(pw)) {// 다 맞는경우
                if (save_id.equals("Y")) {
                    resp.addCookie(CookieUtils.createCookie("SAVE_ID", id, "/", 3600 * 24 * 7));
                }
                session.setAttribute("USER_INFO", user);
                String prePage=(String)session.getAttribute("PRE_PAGE");
                if(prePage!=null) {
                    return "redirect:"+prePage;
                }
                return "redirect:" + "/";
            } else {// 비번만 틀린경우
                return "redirect:" + "/login/login.wow?msg="
                        + URLEncoder.encode("아이디 또는 비번확인", "utf-8");
            }

        }
    }
}

 

 

 

 

 

※같은Controller의 있는 메소드2개니까  그냥 필드에 prePage선언해서 사용하면 되지않을까요??? 

왜 굳이 session에 담아서 사용했나요?

사용자는 여러명인데  모든 사용자의 요청을 LoginController 객체(빈) 1개가 처리합니다.

필드로 선언했을 경우 다음과 같은 문제가 있습니다.

@Controller
public class LoginController {

	String prePage="";
    @GetMapping("/login/login.wow")
    public String loginGet(HttpServletRequest req) {
        String prePage=req.getHeader("referer");
        return "login/login";
    }
    
    @PostMapping("/login/login.wow")
    public String loginPost(HttpServletRequest req, HttpServletResponse resp,HttpSession session) throws Exception {
    //생략
    
    //다 맞은 경우
    return "redirect:"+prePage
    
    }

만약 사용자1이 freeView에서 댓글 등록버튼을 눌렀습니다.

그리고 @GetMapping("/login/login.wow")메소드가

실행되고 이 때 LoginController 객체의 필드 prePage=freeView가 됩니다.

사용자1이 id,pw입력하기전에  사용자2가 MemberList에서 로그인버튼을 눌렀습니다.

이러면 필드prePage=memberList가 됩니다.   그리고 사용자1이 id,pw를 다 입력하면  

짜잔..사용자1은  memberList로 이동하게 됩니다.

그래서 보통 빈객체에서는  데이터변경이 일어나는 변수를 필드로 선언하지않습니다.

주입관련한 빈은 필드에 선언해도 됩니다.