JSP

[JSP] Comment

shb 2022. 4. 13. 18:29

* 게시판 댓글 기능 (JSON + AJAX 사용)
JSON resopnse + AJAX 를 사용한 댓글

 

* 구현할 댓글 기능
- 댓글 목록 
- 댓글 작성
- 댓글 삭제 : 삭제는 한번 confirm 하고 진행

 

* ERD, DDL 작성

* 라이브러리 준비

* select 쿼리문 준비
# 특정글 의 (댓글 + 사용자) 정보

필요한 쿼리문 작성
D.java

	// ★ 댓글
	// 댓글과 작성자 SELECT
	public static final String SQL_COMMENT_SELECT = 
			"SELECT c.uid \"uid\", c.content \"content\", c.regdate \"regdate\",\r\n"
			+ "	u.uid \"user_uid\", u.username \"user_username\", u.password \"user_password\", u.name \"user_name\", u.authorities \"user_authorities\", u.regdate \"user_regdate\"\r\n"
			+ "FROM t2_comment c, t2_user u\r\n"
			+ "WHERE c.user_uid = u.uid "
			;
	
	// '특정 글'의 댓글과 작성자 정보 SELECT
	public static final String SQL_COMMENTS_BY_WRITE =
			SQL_COMMENT_SELECT
			+ "AND c.write_uid = ?\r\n"
			+ "ORDER BY c.uid DESC"
			;
	
	// 특정글에 댓글 추가
	public static final String SQL_COMMENT_INSERT = 
			"INSERT INTO t2_comment(user_uid, write_uid, content) VALUES"
			+ "(?, ?, ?)";	
	
	// 특정 댓글 삭제
	public static final String SQL_COMMENT_DELETE_BY_UID = 
			"DELETE FROM t2_comment WHERE uid = ?";


* CommentDTO 생성

package domain;

import java.time.LocalDateTime;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class CommentDTO {
	private int uid;   // uid
	private String content;
	// java.time.* 객체 변환을 위한 annotation
	@JsonDeserialize(using = LocalDateTimeDeserializer.class)
	@JsonSerialize(using = LocalDateTimeSerializer.class)
	@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")
	@JsonProperty("regdate")
	private LocalDateTime regDate;
	private UserDTO user;    // user_uid (FK) 작성자
	@JsonIgnore
	private WriteDTO write;  // write_uid (FK) 글
}

* CommentDAO 생성

package domain;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

import common.D;

public class CommentDAO {
	Connection conn;
	PreparedStatement pstmt;
	Statement stmt;
	ResultSet rs;
	
	// DAO 객체가 생성될때 Connection 도 생성된다
	public CommentDAO(){
		try {
			Class.forName(D.DRIVER);
			conn = DriverManager.getConnection(D.URL, D.USERID, D.USERPW);
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		} // end try
	}// 생성자
	
	// DB 자원 반납 메소드, 만들어놓으면 편함..
	public void close() throws SQLException{
		if(rs != null) rs.close();
		if(pstmt != null) pstmt.close();
		if(stmt != null) stmt.close();
		if(conn != null) conn.close();
	} // end close();
	
	// 댓글목록읽기
	// ResultSet --> List<DTO> 로 리턴
	public List<CommentDTO> buildList(ResultSet rs) throws SQLException{
		List<CommentDTO> list = new ArrayList<>();
		
		while(rs.next()) {
			int uid = rs.getInt("uid");
			String content = rs.getString("content");
			if(content == null) content = "";
			LocalDateTime regDate = rs.getObject("regdate", LocalDateTime.class); 			
			
			// user_id 의 User 정보 가져오기
			UserDTO user = UserDTO.builder()
					.uid(rs.getInt("user_uid"))
					.username(rs.getString("user_username"))
					.password(rs.getString("user_password"))
					.name(rs.getString("user_name"))
					.authorities(rs.getString("user_authorities"))
					.regdate(rs.getObject("user_regdate", LocalDateTime.class))
					.build()
					;
			
			CommentDTO dto = CommentDTO.builder()
					.uid(uid)
					.content(content)
					.regDate(regDate)
					.user(user)
					.build()
					;
			
			list.add(dto);
			
		} // end while
		
		return list;
	}
	
	
	// 특정글의 댓글들 SELECT
	public List<CommentDTO> selectByWrite(int write_uid) throws SQLException {
		List<CommentDTO> list = null;
		try{
			pstmt = conn.prepareStatement(D.SQL_COMMENTS_BY_WRITE);
			pstmt.setInt(1, write_uid);
			rs = pstmt.executeQuery();
			list = buildList(rs);
		}finally{
			close();
		} // end try
		return list;		
	} // end selectByWrite()
	
	
	// 댓글 작성 <-- DTO
	public int insert(CommentDTO dto) throws SQLException {
		int cnt = 0;
		
		String content = dto.getContent();
		UserDTO user = dto.getUser();  // 댓글 작성자 (로그인 한 사용자)
		WriteDTO write = dto.getWrite();  // 댓글이 달린 글 
		
		
		int uid;  // auto-generated keys 값
		// auto-generated 컬럼
		String [] generatedCols = {"uid"};  

		try{	
			pstmt = conn.prepareStatement(D.SQL_COMMENT_INSERT, generatedCols);
			pstmt.setInt(1, user.getUid());
			pstmt.setInt(2, write.getUid());
			pstmt.setString(3, content);
			cnt = pstmt.executeUpdate();
			
			if(cnt > 0){
				// auto-generated keys 값 뽑아오기
				rs = pstmt.getGeneratedKeys();
				if(rs.next()) {
					uid = rs.getInt(1);
					dto.setUid(uid);  // DTO 에 set
				}
			}
			
		}finally{
			close();
		} // end try
		
		
		return cnt;
	} // end insert()
	
	
	// 특정 댓글 삭제하기 <-- uid
	public int deleteByUid(int uid) throws SQLException {
		int cnt = 0;
		
		try{
			pstmt=conn.prepareStatement(D.SQL_COMMENT_DELETE_BY_UID);
			pstmt.setInt(1, uid);
			cnt = pstmt.executeUpdate();
		}finally{
			close();
		} // end try
		
		return cnt;
	} // end deleteByUid()
	
	
	
} // end DAO


* command.comment
CmdListCommand.java
CmdWriteCommand.java
CmdDeleteCommand.java

 


* CommentController 생성
이번에는 페이지를 response 하는게 아니다. (viewPage 가 필요없다)
걍  각 Command 를 execute() 만 해주면 된다.

package controller;

import java.io.IOException;

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 command.Command;
import command.comment.CmtDeleteCommand;
import command.comment.CmtListCommand;
import command.comment.CmtWriteCommand;

@WebServlet("/comment/*")
public class CommentController extends HttpServlet {
	private static final long serialVersionUID = 1L;
       
    public CommentController() {
        super();
    }

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doAction(request, response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		request.setCharacterEncoding("UTF-8");
		doAction(request, response);
	}

	protected void doAction(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		System.out.println("CommentController.actionDo() 호출");
		
		String method = request.getMethod();
				
		// URL로부터 URI, ContextPath, Command 분리
		String uri = request.getRequestURI();
		String conPath = request.getContextPath() + "/comment";
		String com = uri.substring(conPath.length());
		
		// 테스트 출력
		System.out.println("uri: " + uri);  
		System.out.println("conPath: " + conPath);  
		System.out.println("com: " + com);

		Command command = null;   // 어떠한 로직을 수행할지

		switch(com) {
		case "/list":
			new CmtListCommand().execute(request, response);
			break;
		case "/write":
			if(method.equals("POST")) {
				new CmtWriteCommand().execute(request, response);
			}
			break;
		case "/delete":
			if(method.equals("POST")) {
				new CmtDeleteCommand().execute(request, response);
			}
			break;
		} // end switch
	
	
	} // end doAction()
	
	

} // end Controller


* Response 할 객체 준비 (공통)
일단 QryResult.java 객체
'공통적'으로  response 할 내용들을 필드로 담아서 정의
(@Data, @NoArgsConstructor  두개만)

package domain;

import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
public class QryResult {
	int count;  // 결괏값 
	String status;  // 결과 메세지
}

* Response 할 객체 준비 ( 댓글 List  Response)

package domain;

import java.util.List;

import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
public class QryCommentList extends QryResult {
	List<CommentDTO> list;
}


댓글 목록
특정글의 댓글 목록 가져와서 response 하기
CmtListCommand.java 작성

package command.comment;

import java.io.IOException;
import java.sql.SQLException;
import java.util.List;

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

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;

import command.Command;
import domain.CommentDAO;
import domain.CommentDTO;
import domain.QryCommentList;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

public class CmtListCommand implements Command {

	@Override
	public void execute(HttpServletRequest request, HttpServletResponse response) throws IOException {
		
//		AjaxWriteResult obj = AjaxWriteResult.builder()
//				.num(177)
//				.name("홍길동")
//				.arr(new int[] {10, 20, 30})
//				.cities(Arrays.asList("서울", "인천", "부산"))
//				.item2(new AjaxItem(10, "사과"))
//				.build()				
//				;
		
		int uid = Integer.parseInt(request.getParameter("uid"));
		
		QryCommentList obj = new QryCommentList();
		ObjectMapper mapper = new ObjectMapper();   // JSON 매핑할 Mapper 객체
		
		
		try {
			List<CommentDTO> list = new CommentDAO().selectByWrite(uid);
			obj.setCount(list.size());
			obj.setStatus("OK");
			obj.setList(list);
		} catch (SQLException e) {
			e.printStackTrace();
			obj.setStatus("댓글목록 ERROR: " + e.getMessage());
		}
		
		String output = mapper.writeValueAsString(obj);  // JSON String <- Java Object
		System.out.println("output : " + output);
		response.setContentType("application/json; charset=utf-8");  // MIME 설정
		response.getWriter().write(output);

	}

}

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
class AjaxWriteResult {
	private int num;   // -> number 로
	
	@JsonProperty("Name")  // JSON 으로 출력되는 이름지정
	private String name; // -> string
	private int[] arr;  // ->  array 로
	@JsonIgnore   // JSON 변환에서 제외
	private List<String> cities; // -> array 로 변환
	
	public int getAge() {
		return 43;
	}
	
	// -> object 로 변환
	AjaxItem item1;
	AjaxItem item2;
	
}

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
class AjaxItem {
	private int stock;
	private String product;
}


java.time.* 처리해주기
여기서 CommentDTO List 를 넣으면 response 할때 에러 난다.
1. Java8 의 Date/Time 객체를 위한 Jackson 의 추가 모듈 설치 +
2. java.time.* 에 대한 annotation  설정이  필요.하다
일단 server stop 하고 라이브러리 설치

 

* Jackson + java.time.*     annotaion 설정하기

* 불필요한 필드에 @JsonIgnore 처리하기

 

* 일단 하단에 댓글 html 기본양식 붙이기
board/comment.jsp 생성,  댓글 기본 양식
그리고 board/view.jsp  에 include 시키기 그리고 결과 확인 

 

* ROLE_MEMBER 가진 사용자만 댓글 작성 가능케 하기
comment.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>   
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> 

<script src="https://kit.fontawesome.com/51772bd9bd.js" crossorigin="anonymous"></script>
    
<div class="container my-3 border rounded">
    <div class="mb-3 mt-3">
        <label>댓글: <span id="cmt_cnt"></span> 개</label>
        
        <%-- ROLE_MEMBER 권한 가진 사용자만 댓글 작성 가능 --%>
        <c:if test="${fn:contains(sessionScope.PRINCIPAL.authorities, 'ROLE_MEMBER') }">
        <div class="input-group my-2">
            <input type="text" class="form-control" id="input_comment">
            <button type="button" class="btn btn-outline-primary" id="btn_comment">작성</button>
        </div>
        </c:if>

        <table class="table table-hover mt-3" id="cmt_table">
            <thead>
              <tr>
                <th style="width: 16.66%">작성자</th>
                <th>내용</th>
                <th style="width: 16.66%">작성일</th>
              </tr>
            </thead>
            <tbody id="cmt_list">

            </tbody>
        </table>
    </div>
</div>    

 

* 확인 후에는 일부 내용 삭제하기
댓글개수, <tbody> 안의 내용

 

* js/view.js 파일 생성
* board/view.jsp 에는
 jQuery 와 함께 js 파일 포함
contextPath 와 현재 로그인한 user 의 uid 정보가 js 에 넘겨져야 하므로 jsp 파링레 설정
view.js 을 include 

 

* view.js  작성
ajax() request - response 하기
댓글 목록 렌더링 해주기
작성자 본인인 경우에만 삭제 버튼 보여주기
댓글 작성 input 은 ROLE_MEMBER 권한을 가진이에게만 보여주기

 


* 댓글 작성
다음의 항목들이 서버로 전달되어야 한다
1. 어느글에 대한 댓글인지?
2. 어느 사용자가 작성한 댓글인지?
3. 댓글 내용은 무엇인지?
서버에서 INSERT 트랜잭션후 다시 댓글 list 를 불러와서 update 를 해야 한다.

 

* 댓글 작성 버튼 누를시 동작
view.js 의 document.ready() 에 등록
1. 입력한 값 검증
2. 전달할 parameter 준비
3. ajax request - response
4. response 후 댓글 목록 업데이트

 

* CmtWriteCommand 작성
CmtWriteCommand.java 작성

 

package command.comment;

import java.io.IOException;
import java.sql.SQLException;

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

import com.fasterxml.jackson.databind.ObjectMapper;

import command.Command;
import domain.CommentDAO;
import domain.CommentDTO;
import domain.QryResult;
import domain.UserDTO;
import domain.WriteDTO;

public class CmtWriteCommand implements Command {

	@Override
	public void execute(HttpServletRequest request, HttpServletResponse response) throws IOException {
		int writeUid = Integer.parseInt(request.getParameter("write_uid"));  // 어느글에 대한 댓글?
		int userUid = Integer.parseInt(request.getParameter("user_uid"));  // 누가 쓰는 댓글?
		String content = request.getParameter("content");
		
		WriteDTO write = new WriteDTO();
		write.setUid(writeUid);
		UserDTO user = new UserDTO();
		user.setUid(userUid);

		CommentDTO dto = CommentDTO.builder()
				.write(write)
				.user(user)
				.content(content)
				.build();
		
		QryResult obj = new QryResult();  // response 할 Java 객체
		ObjectMapper mapper = new ObjectMapper();  // JSON 으로 매핑할 Mapper 객체
		
		try {
			int cnt = new CommentDAO().insert(dto);
			obj.setCount(cnt);
			obj.setStatus("OK");
		} catch (SQLException e) {
			e.printStackTrace();
			obj.setStatus("댓글 작성 ERROR: " + e.getMessage());
		}
		
		String output = mapper.writeValueAsString(obj);  // JSON 문자열 변환
		response.setContentType("application/json; charset=utf-8"); // MIME 설정
		response.getWriter().write(output);  // 전송
		
	}

}

* 댓글 삭제

1. 몇번(uid) 댓글이 삭제되는 것인가?
2. 삭제하기 전에 한번 confirm 시키기
3. 댓글 삭제 버튼
명심! : 댓글목록을 불러온 후에, 삭제 버튼에 대한 이벤트 리스터를 동작시켜야 한다.

 

view.js 에 addDelete() 함수

 

* CmtDeleteCommand 작성

package command.comment;

import java.io.IOException;
import java.sql.SQLException;

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

import com.fasterxml.jackson.databind.ObjectMapper;

import command.Command;
import domain.CommentDAO;
import domain.QryResult;

public class CmtDeleteCommand implements Command {

	@Override
	public void execute(HttpServletRequest request, HttpServletResponse response) throws IOException {
		int uid = Integer.parseInt(request.getParameter("uid"));
		
		QryResult obj = new QryResult();
		ObjectMapper mapper = new ObjectMapper();
		
		try {
			int cnt = new CommentDAO().deleteByUid(uid);
			obj.setCount(cnt);
			obj.setStatus("OK");
		} catch (SQLException e) {
			e.printStackTrace();
			obj.setStatus("댓글 삭제 ERROR: " + e.getMessage());
		}
		
		String output = mapper.writeValueAsString(obj);  // JSON 문자열 변환
		response.setContentType("application/json; charset=utf-8"); // MIME 설정
		response.getWriter().write(output);  // 전송

	}

}

'JSP' 카테고리의 다른 글

[JSP] File Update, Delete  (0) 2022.04.12
[JSP] FileUpload / Download  (0) 2022.04.11
[JSP] Pagination  (0) 2022.04.11
[JSP] Authentification - 회원제 게시판  (0) 2022.04.10
[JSP] Authentication - 로그인/로그아웃  (0) 2022.04.10