감 잃지말고 개발하기

[JSP] [MVC] [AJAX] [JS] [JSON] AJAX를 이용한 비동기 통신으로 댓글/대댓글 구현하기 #2 댓글 Create 본문

JSP/MVC

[JSP] [MVC] [AJAX] [JS] [JSON] AJAX를 이용한 비동기 통신으로 댓글/대댓글 구현하기 #2 댓글 Create

persii 2023. 4. 12. 23:09

MVC패턴을 지키면서 특정 페이지에서 ajax를 이용한 비동기 통신으로 댓글을 작성하고 모든 댓글을 출력하는 로직을 기록하고자 한다.

지난 포스팅에 이어 이번 포스팅에는 댓글(원글)을 작성하고 저장하는 로직을 기록해 보기로 한다.

 

목표

 JSP에서 MVC 패턴을 지키면서 AJAX를 통해 댓글(원글) 저장 로직을 구현할 수 있다.

♠ DB에서 댓글 테이블에 필요한 그룹 칼럼, 깊이 칼럼, 출력 순서 칼럼을 이해할 수 있다. 

 

 

DB 테이블 생성

DB는 MySQL8.0을 사용했다.

테이블 명은 comment로 설정하고, PK 칼럼은 c_id이다.

CREATE TABLE `jspbookshop`.`comment` (
  `c_id` INT NOT NULL AUTO_INCREMENT COMMENT '코멘트 아이디',
  `c_b_id` INT NOT NULL COMMENT '도서 아이디',
  `c_m_id` VARCHAR(15) NOT NULL COMMENT '코멘트를 작성하는 회원 아이디',
  `c_title` VARCHAR(45) NOT NULL COMMENT '코멘트 제목',
  `c_text` VARCHAR(2000) NOT NULL COMMENT '코멘트 내용',
  `c_regdate` DATE NOT NULL COMMENT '코멘트 등록 날짜',
  `c_empathy` INT NULL COMMENT '공감 수',
  `c_ref` INT NULL COMMENT '그룹번호',
  `c_lev` INT NULL COMMENT '깊이',
  `c_seq` INT NULL COMMENT '관련글 중 출력 순서',
  PRIMARY KEY (`c_id`));

테이블 생성

 

 

로직 흐름

  1. 도서 상세 페이지의 댓글 작성 Form에서 댓글 제목과 내용을 입력하고 "등록하기" 버튼을 누르면 스크립트 함수가 실행되고, ajax 함수로 Form의 정보가 서버에 전송된다(bookView.jsp).
  2. 해당 컨트롤러에서 해당 URL을 받아 댓글 작성을 처리하는 클래스를 호출한다(ReviewFrontController.java).
  3. Action 클래스에서 비즈니스 로직을 처리하는 클래스를 호출하여 댓글을 DB에 저장시키고, DB처리 결과에 따라 응답 처리를 한다(BookCommentRegistProAction.java).
  4. Service 클래스에서 DAO클래스를 호출한다(BookCommentRegistProService.java).
  5. DAO 클래스에서 MySQL8.0 DB에 접근해 데이터를 가져온다(CommentDAO).
  6. 다시 도서 상세 페이지로 돌아와 적절한 응답을 한다.

 

 

로직 흐름 코드 및 실행화면

1. 도서 상세 페이지(bookView.jsp)

1-1. 도서 상세 페이지 댓글 작성 영역

Form 태그에 댓글 제목과 내용을 입력하고 "등록하기" 버튼을 누르면 스크립트 함수가 실행된다.

댓글 작성

 

1-2. 도서 상세 페이지 댓글 작성 부분 HTML

댓글 작성 Form

 

1-3. 등록하기 버튼 클릭 JS 코드

댓글 작성 Form에서 "등록하기" 버튼을 클릭하면 실행된다. 

 

jQuery 문법으로 ajax를 사용한다.

  • ajax로 호출할 URL : "/bookCommentRegistPro.re"
  • ajax로 보낼 데이터 통신타입 : POST
  • ajax로 서버에 요청 시 보낼 매개변수 : queryString
    • serialize()를 이용해 form 태그 안의 모든 값들의 name값을 키값으로 만들어 보내준다.
  • 응답받을 데이터 타입 : TEXT
    • 서버에서 DB insert에 대한 결과물(문자열)을 response에 담아 보낼 것이므로 dataType을 TEXT로 설정한다.
  • 정상적으로 요청/응답된 경우(success) : 서버에서 보낸 text 인자 값이 "success"일 때 alert창을 띄운다.
<script type="text/javascript">

    // 댓글 작성
    $(".registCommentBtn").click(function() {

        var queryString = $("form[name=commentForm]").serialize();

        $.ajax({
            url: "/bookCommentRegistPro.re",
            type: "POST",
            data: queryString,
            dataType: "TEXT",	// 서버에서 DB insert에 대한 결과물을 받을 것
            success: function(text) {
                if(text === "success") {
                    alert("등록되었습니다.");
                    $("#c_title").attr("readonly", "readonly");
                    $("#c_text").attr("readonly", "readonly");
                } else {
                    alert("등록되지 못했습니다.");
                    return false;
                }
            }, 
            error: function(request, status, error) {
                console.log("code: "+request.status+"\n"+"message : "+
                			request.responseText+"\n"+"error: "+error);
            } 
        });
    });
</script>

 

2. 컨트롤러(ReviewFrontController.java)

웹 브라우저에서 요청한 URL인 /bookCommentRegistPro.re에 해당하는 if문으로 들어오고 BookCommentRegistProAction 클래스의 execute() 메서드가 호출된다. 

 

AJAX 통신을 처리하는 컨트롤러이기 때문에 페이지 이동 처리 정보를 담는 ActionForward 클래스가 필요 없다.

또한, 페이지 이동 처리 역시 필요 없다.

package controller;

import java.io.IOException;
import java.rmi.ServerException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import action.BookReviewListAction;
import action.BookReviewRegistProAction;
import svc.AjaxAction;

/** AJAX로 데이터 통신
 * ActionForward 클래스 필요 X */
@WebServlet("*.re")
public class ReviewFrontController extends HttpServlet{
	
	protected void doProcess(HttpServletRequest req, HttpServletResponse resp) 
			throws ServerException, IOException, ServletException {
		
		req.setCharacterEncoding("UTF-8");
		
		/* 1. 요청 주소 파악 */		
		String requestURI = req.getRequestURI();
		String contextPath = req.getContextPath();
		String command = requestURI.substring(contextPath.length());
	
		System.out.println(" R.Front.C : 1. 요청 주소 계산 완료");

		/* 2. 각 요청 주소의 매핑 처리 */
		AjaxAction action = null;

		// 코멘트 등록 처리 요청
		else if (command.equals("/bookCommentRegistPro.re")) {
			action = new BookCommentRegistProAction();
			try {
				action.execute(req, resp);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		System.out.println(" R.Front.C : 2. 요청 주소 매칭 끝");		
	}

	protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
			throws ServerException, IOException, ServletException {
		doProcess(req, resp);
	}

	protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
			throws ServerException, IOException, ServletException {
		doProcess(req, resp);
	}
}

 

3. Action 클래스(BookCommentRegistProAction.java)

  1. ajax로 넘어온 데이터를 Comment 객체에 저장한다.
  2. DB에 접근하여 Comment 객체를 DB에 저장한다.
  3. DB 처리 결과에 따른 결과값을 response에 담아 보낸다.   
package action;

import java.io.PrintWriter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import svc.AjaxAction;
import svc.BookCommentRegistProService;
import util.Util;
import vo.Comment;

/** b_id에 따른 코멘트를 처리하는 Action 클래스 */
public class BookCommentRegistProAction implements AjaxAction {

	@Override
	public void execute(HttpServletRequest req, HttpServletResponse resp) throws Exception {

		/* ajax로 넘어온 데이터를 Comment 객체에 저장*/
		Comment comment = new Comment();
		comment.setC_b_id(Integer.parseInt(req.getParameter("c_b_id")));
		comment.setC_m_id(req.getParameter("c_m_id"));
		comment.setC_title(req.getParameter("c_title"));
		comment.setC_text(req.getParameter("c_text"));
		
		/* DB에 접근해 데이터 등록 */
		BookCommentRegistProService service = new BookCommentRegistProService();
		boolean isRegistSuccess = service.insertComment(comment);

		/* DB 처리에 따른 결과값 리턴 */
		PrintWriter out = resp.getWriter();
		if(isRegistSuccess) {
			out.print("success");
		} else {
			out.print("failed");
		}
	}
}

 

4. Service 클래스(BookCommentRegistProService.java)

  1. CommentDAO 클래스의 insertComment() 메서드를 호출해 Action 클래스에서 넘어온 파라미터를 인자로 넣어준다. 
  2. insertComment() 메서드가 성공적으로 실행된 경우, DB에 영구반영하고 isRegistSuccess 변수를 "true"로 설정한 후 리턴한다.
package svc;

import static db.JdbcUtil.close;
import static db.JdbcUtil.commit;
import static db.JdbcUtil.getConnection;
import static db.JdbcUtil.rollback;

import java.sql.Connection;

import dao.CommentDAO;
import vo.Comment;

/** 코멘트 등록을 처리하는 Service 클래스 */
public class BookCommentRegistProService {

	/** 코멘트를 등록하는 메서드 */
	public boolean insertComment(Comment comment) {
    
		boolean isRegistSuccess = false;
		
		/* DB 작업 
		 * 1. 커넥션 풀에서 Connection 객체 얻어오기
		 * 2. 싱글톤 방식으로 CommentDAO 클래스의 인스턴스를 get 
		 * 3. CommentDAO 클래스에서 DB 작업을 수행할 때 사용할 Connection 객체를 주입 
		 * 4. DB에 새로운 리뷰 삽입(INSERT) 메서드 호출
		 * */
		Connection conn = getConnection();
		CommentDAO commentDAO = CommentDAO.getInstance();
		commentDAO.setConnection(conn);
		int insertCount = commentDAO.insertComment(comment);
		
		if(insertCount > 0) {
			// 성공적으로 삽입된 경우. 트랜잭션 영구반영
			commit(conn);
			isRegistSuccess = true;
		} else {
			rollback(conn);
		}
		close(conn);
		return isRegistSuccess;
	}
}

 

5. DAO 클래스(CommentDAO.java)

  1. 그룹 번호(c_ref)를 c_id와 같게 하기 위해 selectMaxPk() 메서드를 호출해 c_id의 max값을 구한다.
  2. PreparedStatement 객체를 생성해 인자로 건너온 Comment 객체를 테이블에 추가하는 INSERT 구문을 실행한다. 
    • INSERT INTO jspbookshop.comment VALUES(NULL,?,?,?,?,now(),0,?,0,0)
      • 칼럼 순서대로 값을 넣어준다.  
      • 첫 번째 값 : c_id는 Auto Increment로 설정했으므로 NULL로 넣어준다. 
      • 두 번째 값 : c_b_id(댓글을 다는 도서 아이디)
      • 세 번째 값 : c_m_id(댓글을 다는 유저 아이디)
      • 네 번째 값 : c_title(댓글 제목)
      • 다섯 번째 값 : c_text(댓글 내용)
      • 여섯 번째 값 : c_regdate(댓글 등록날짜)
      • 일곱 번째 값 : c_ref(그룹번호). c_id와 같게 설정해 주기 위해 selectMaxPk()로 넘어온 값을 넣어준다.
      • 여덟 번째 값 : c_lev(댓글 깊이). 댓글(원글)이므로 0으로 설정해 준다.
      • 아홉 번째 값 : c_seq(댓글 출력순서). 댓글(원글)이므로 0으로 설정해 준다.
  3. 삽입된 결과를 리턴한다. 
package dao;

import static db.JdbcUtil.*;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;

import vo.Comment;

public class CommentDAO {

	Connection conn;
	private static CommentDAO commentDAO;

	private CommentDAO() {
	}
	
	/** 싱글톤 패턴으로 CommentDAO 객체를 생성하여 리턴하는 메서드 */
	public static CommentDAO getInstance() {
		if(commentDAO == null) {
			commentDAO = new CommentDAO();
		}
		return commentDAO;
	}
	
	/** CommentDAO 객체에 Connection 객체를 주입하는 메서드 */
	public void setConnection(Connection conn) {
		this.conn = conn;
	}
	
	/** 가장 최근에 등록된 c_id 구하는 메서드 */
	public int selectMaxPk() {
		
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		String sql = "select max(c_id) from jspbookshop.comment";
		int maxPk = 0;
		
		try {
			pstmt = conn.prepareStatement(sql);
			rs = pstmt.executeQuery();
			if(rs.next()) maxPk = rs.getInt(1);
			else maxPk = 1;
		} catch (Exception e) {
			System.out.println(" C.DAO : selectMaxPk() ERROR : "+e);
		} finally {
			close(rs);
			close(pstmt);
		}
		return maxPk;
	}
	
	/** 코멘트 등록하는 메서드 */
	public int insertComment(Comment comment) {

		PreparedStatement pstmt = null;
		int insertCount = 0;
        
		try {
			// 가장 최근에 등록된 c_id 구하기
			int maxPk = selectMaxPk();
			
			String sql = "insert into jspbookshop.comment ";
				sql += "values(NULL,?,?,?,?,now(),0,?,0,0)";
			pstmt = conn.prepareStatement(sql);
			pstmt.setInt(1, comment.getC_b_id());
			pstmt.setString(2, comment.getC_m_id());
			pstmt.setString(3, comment.getC_title());
			pstmt.setString(4, comment.getC_text());
			pstmt.setInt(5, maxPk+1);
			
			insertCount = pstmt.executeUpdate();
		} catch (Exception e) {
			System.out.println(" C.DAO : insertComment() ERROR : "+e);
		} finally {
			close(pstmt);
		}
		return insertCount;
	}
}

 

6. 도서 상세 페이지(bookView.jsp)

ajax로 보낸 요청이 정상적으로 처리되고, 서버로부터 정상적인 응답이 이루어졌다면 DB에 댓글이 저장되고, 화면에 alert창이 뜬다.

댓글 작성 alert창
댓글의 DB commit

 

이렇게 댓글(원글) 작성 로직이 구현되었다. 다음 시간에는 ajax 방식으로 댓글을 화면에 출력하는 로직을 기록해 보겠다.

 

 

끝.