게시글 상세 조회
📍 제목, 내용, 작성자 등 조회
📍 이미지 조회
<a href="detail?no=${board.boardNo}&cp=${pagination.currentPage}&type=${param.type}${sURL}">${board.boardTitle}</a>
package edu.kh.community.board.controller;
import java.io.IOException;
import java.util.List;
import javax.servlet.RequestDispatcher;
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 edu.kh.community.board.model.service.BoardService;
import edu.kh.community.board.model.service.ReplyService;
import edu.kh.community.board.model.vo.BoardDetail;
import edu.kh.community.board.model.vo.Reply;
@WebServlet("/board/detail")
public class BoardDetailServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
// 파라미터 중 게시글 번호(no) 얻어오기
int boardNo = Integer.parseInt(req.getParameter("no"));
BoardService service = new BoardService();
// 게시글 정보 + 이미지 리스트 조회
BoardDetail detail = service.selectBoardDetail(boardNo);
// 게시글 상세조회된 내용이 있을 경우 댓글 목록 조회
if(detail != null) {
List<Reply> rList = new ReplyService().selectReplyList(boardNo);
req.setAttribute("rList", rList);
}
req.setAttribute("detail", detail);
String path = "/WEB-INF/views/board/boardDetail.jsp";
RequestDispatcher dispatcher = req.getRequestDispatcher(path);
dispatcher.forward(req, resp);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/** 게시글 상세 조회 service
* @param boardNo
* @return detail
* @throws Exception
*/
public BoardDetail selectBoardDetail(int boardNo) throws Exception {
Connection conn = getConnection();
// 1) 게시글 (BOARD 테이블) 관련 내용만 조회
BoardDetail detail = dao.selectBoardDetail(conn, boardNo);
if(detail != null) { // 게시글 상세 조회 결과가 있을 경우
// 2) 게시글에 첨부된 이미지(BOARD_IMG 테이블 조회)
List<BoardImage> imageList = dao.selectImageList(conn, boardNo);
// 조회된 imageList를 BoardDetail 객체에 세팅
detail.setImageList(imageList);
}
close(conn);
return detail;
}
/** 게시글 상세 조회 DAO
* @param conn
* @param boardNo
* @return detail
* @throws Exception
*/
public BoardDetail selectBoardDetail(Connection conn, int boardNo) throws Exception{
BoardDetail detail=null;
try {
String sql = prop.getProperty("selectBoardDetail");
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, boardNo);
rs = pstmt.executeQuery();
if(rs.next()) {
detail = new BoardDetail();
detail.setBoardNo(rs.getInt(1));
detail.setBoardTitle(rs.getString(2));
detail.setBoardContent(rs.getString(3));
detail.setCreateDate(rs.getString(4));
detail.setUpdateDate(rs.getString(5));
detail.setReadCount(rs.getInt(6));
detail.setMemberNickname(rs.getString(7));
detail.setProfileImage(rs.getString(8));
detail.setMemberNo(rs.getInt(9));
detail.setBoardName(rs.getString(10));
}
} finally {
close(rs);
close(pstmt);
}
return detail;
}
<!-- 특정 게시글 상세 조회 -->
<entry key="selectBoardDetail">
SELECT BOARD_NO, BOARD_TITLE, BOARD_CONTENT,
TO_CHAR(CREATE_DT,'YYYY"년" MM"월" DD"일" HH24:MI:SS') CREATE_DT,
TO_CHAR(UPDATE_DT,'YYYY"년" MM"월" DD"일" HH24:MI:SS') UPDATE_DT,
READ_COUNT, MEMBER_NICK, PROFILE_IMG, MEMBER_NO, BOARD_NM
FROM BOARD
JOIN MEMBER USING(MEMBER_NO)
JOIN BOARD_TYPE USING(BOARD_CD)
WHERE BOARD_NO = ?
AND BOARD_ST = 'N'
</entry>
/** 게시글에 첨부된 이미지 리스트 조회 DAO
* @param conn
* @param boardNo
* @return imageList
* @throws Exception
*/
public List<BoardImage> selectImageList(Connection conn, int boardNo) throws Exception {
List<BoardImage> imageList = new ArrayList<>();
try {
String sql = prop.getProperty("selectImageList");
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, boardNo);
rs = pstmt.executeQuery();
while(rs.next()) {
BoardImage image = new BoardImage();
image.setImageNo(rs.getInt(1));
image.setImageReName(rs.getString(2));
image.setImageOriginal(rs.getString(3));
image.setImageLevel(rs.getInt(4));
image.setBoardNo(rs.getInt(5));
imageList.add(image);
}
} finally {
close(rs);
close(pstmt);
}
return imageList;
}
<!-- 게시글에 첨부된 이미지 리스트 조회 -->
<entry key="selectImageList">
SELECT * FROM BOARD_IMG
WHERE BOARD_NO=?
ORDER BY IMG_LEVEL
</entry>
/** 댓글 목록 조회 service
* @param boardNo
* @return replyList
* @throws Exception
*/
public List<Reply> selectReplyList(int boardNo) throws Exception{
Connection conn = getConnection();
List<Reply> replyList = dao.selectReplyList(conn, boardNo);
close(conn);
return replyList;
}
/** 댓글 목록 조회 DAO
* @param conn
* @param boardNo
* @return replyList
* @throws Exception
*/
public List<Reply> selectReplyList(Connection conn, int boardNo) throws Exception {
List<Reply> replyList = new ArrayList<Reply>();
try {
String sql = prop.getProperty("selectReplyList");
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, boardNo);
rs = pstmt.executeQuery();
while(rs.next()) {
Reply r = new Reply();
r.setReplyNo(rs.getInt("REPLY_NO"));
r.setReplyContent(rs.getString(2));
r.setCreateDate(rs.getString(3));
r.setBoardNo(rs.getInt(4));
r.setMemberNo(rs.getInt(5));
r.setMemberNickname(rs.getString(6));
r.setProfileImage(rs.getString(7));
replyList.add(r);
}
} finally {
close(rs);
close(pstmt);
}
return replyList;
}
댓글 관련 코드는 다음 게시물애 이어서
📍; 뒤로가기
📍 history.go(숫자) : 양수(앞으로 가기), 음수(뒤로 가기)
<%@ 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" %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>게시판</title>
<link rel="stylesheet" href="${contextPath}/resources/css/boardDetail-style.css">
<link rel="stylesheet" href="${contextPath}/resources/css/main-style.css">
<link rel="stylesheet" href="${contextPath}/resources/css/reply-style.css">
<script src="https://kit.fontawesome.com/8f020b2fa9.js" crossorigin="anonymous"></script>
</head>
<body>
<main>
<jsp:include page="/WEB-INF/views/common/header.jsp"/>
<section class="board-detail">
<!-- 제목 -->
<div class="board-title">${detail.boardTitle} <span> - ${detail.boardName}</span></div>
<!-- 프로필 + 닉네임 + 작성일 + 조회수 -->
<div class="board-header">
<div class="board-writer">
<c:if test="${empty detail.profileImage}">
<!-- 프로픨 이미지가 없는 경우 -->
<img src="${contextPath}/resources/images/user.png">
</c:if>
<c:if test="${!empty detail.profileImage}">
<!-- 프로픨 이미지가 있는 경우 -->
<img src="${contextPath}${detail.profileImage}">
</c:if>
<span>${detail.memberNickname}</span>
</div>
<div class="board-info">
<p><span>작성일</span> ${detail.createDate} </p>
<c:if test="${ !empty detail.updateDate}">
<!-- updateDate가 존재하는 경우에만 보여짐 -->
<p><span>마지막 수정일</span> ${detail.updateDate} </p>
</c:if>
<p><span>조회수</span> ${detail.readCount}</p>
</div>
</div>
<!-- 이미지가 있을 경우 -->
<c:if test="${!empty detail.imageList}">
<!-- 썸네일이 있을 경우 변수 생성 -->
<c:if test="${detail.imageList[0].imageLevel == 0}">
<c:set var="thumbnail" value="${detail.imageList[0]}"/>
<!-- page scope(페이지 내 어디서든 사용 가능) -->
</c:if>
</c:if>
<!-- 썸네일 영역(썸네일이 있을 경우) -->
<c:if test="${!empty thumbnail}">
<h5>썸네일</h5>
<div class="img-box">
<div class="boardImg thumbnail">
<img src="${contextPath}${thumbnail.imageReName}">
<a href="${contextPath}${thumbnail.imageReName}" download="${thumbnail.imageOriginal}">다운로드</a>
</div>
</div>
</c:if>
<c:if test="${empty thumbnail}"> <!-- 썸네일 X -->
<c:set var="start" value="0"/>
</c:if>
<c:if test="${!empty thumbnail}"> <!-- 썸네일 O -->
<c:set var="start" value="1"/>
</c:if>
<!-- 업로드 이미지가 있는 경우 -->
<c:if test="${fn:length(detail.imageList) > start}">
<!-- 업로드 이미지 영역 -->
<h5>업로드 이미지</h5>
<div class="img-box">
<c:forEach var="i" begin="${start}" end="${fn:length(detail.imageList)-1}">
<div class="boardImg">
<img src="${contextPath}${detail.imageList[i].imageReName}">
<a href="${contextPath}${detail.imageList[i].imageReName}" download="${detail.imageList[i].imageOriginal}">다운로드</a>
</div>
</c:forEach>
</div>
</c:if>
<!-- 내용 -->
<div class="board-content">
${detail.boardContent}
</div>
<!-- 버튼 영역 -->
<div class="board-btn-area">
<c:if test="${loginMember.memberNo == detail.memberNo}">
<%-- cp가 없을 경우에 대한 처리 --%>
<c:if test="${empty param.cp}">
<!-- 파라미터에 cp가 없을 경우 1 -->
<c:set var="cp" value="1"/>
</c:if>
<c:if test="${!empty param.cp}">
<!-- 파라미터에 cp가 있을 경우 param.cp -->
<c:set var="cp" value="${param.cp}"/>
</c:if>
<button id="updateBtn" onclick="location.href='write?mode=update&type=${param.type}&cp=${cp}&no=${param.no}'">수정</button>
<button id="deleteBtn">삭제</button>
</c:if>
<!--; 뒤로가기
history.go(숫자) : 양수(앞으로 가기), 음수(뒤로 가기)
-->
<button id="goToListBtn" >목록으로</button>
</div>
</section>
<!-- 댓글 -->
<jsp:include page="/WEB-INF/views/board/reply.jsp"/>
</main>
<jsp:include page="/WEB-INF/views/common/footer.jsp"/>
<!-- jQuery 추가 -->
<script src="https://code.jquery.com/jquery-3.7.0.min.js" integrity="sha256-2Pmvv0kuTBOenSvLm6bvfBSSHrUJ+3A7x6P5Ebd07/g=" crossorigin="anonymous"></script>
<script src="${contextPath}/resources/js/board/board.js"></script>
<script>
// 댓글 관련 JS 코드에 필요한 값을 전역변수로 선언
// jsp 파일: html, css, js, el, jstl 사용 가능
// js 파일 : js
// 코드 해석 순서 : EL == JSTL > HTML > JS
// ** JS 코드에서 EL, JSTL을 작성하게 된다면 반드시 ""를 양쪽에 추가 **
// 최상위 주소
const contextPath = "${contextPath}";
// 게시글 번호
const boardNo = "${detail.boardNo}"; // 문자열 "500"
// 로그인한 회원번호
const loginMemberNo = "${loginMember.memberNo}";
// -> 로그인 O : "10";
// -> 로그인 X : ""; (빈문자열)
</script>
<script src="${contextPath}/resources/js/board/reply.js"></script>
</body>
</html>
📍 절대경로, 상대경로 지정법
// 상세조회, 게시글 작성 - 목록으로 버튼
(function(){
const goToListBtn = document.getElementById("goToListBtn");
if(goToListBtn != null){ // 목록으로 버튼이 화면에 있을 때만 이벤트 추가
goToListBtn.addEventListener("click", function(){
// location 객체(BOM)
// 뮨자열.substring(시작, 끝) : 시작 이상 끝 미만 인덱스까지 문자열 자르기
// 문자열.indexOf("검색 문자열", 시작 인덱스)
// : 문자열에서 "검색 문자열"의 위치(인덱스)를 찾아서 반환
// 단, 시작 인덱스 이후부터 검색
const pathname = location.pathname; // 주소상에서 요청 경로 반환
// /community.board.detail
// 이동할 주소 저장
let url = pathname.substring(0, pathname.indexOf("/",1));
// /community
url += "/board/list?"; // /community/board/list?
// url 내장 객체 : 주소 관련 정보를 나타내는 객체
// location.href : 현재 페이지 주소 + 쿼리스트링
// URL.searchParams : 쿼리 스트링만 별도 객체 반환
const params = new URL(location.href).searchParams;
const type = "type=" + params.get("type"); // type=1
let cp;
if(params.get("cp") != "") { // 쿼리스트링에 cp가 있을 경우
cp = "cp=" + params.get("cp"); // cp=1
} else {
cp = "cp=1";
}
//const cp = "cp=" + (params.get("cp") !=null ? params.get("cp") : 1); // cp=1
// 조립
url += type + "&" + cp;
// 검색 key, query가 존재하는 경우, url에 추가
if(params.get("key") != null){
const key = "&key=" + params.get("key");
const query = "&query=" + params.get("query");
url += key + query; // url 뒤에 붙이기
}
// location.href = "주소"; -> 해당 주소로 이동
location.href = url;
})
}
})();
// 즉시 실행 함수 : 성능 up(속도), 변수명 중복 X
(function(){
const deleteBtn = document.getElementById("deleteBtn"); // 존재하지 않으면 null
if(deleteBtn != null) { // 버튼이 화면에 존재할 때만
deleteBtn.addEventListener("click", function(){
// 현재 : /community/board/detail?no=1111&cp=1&type=1
// 목표 : /community/board/delete?no=1111&type=1
// 상대경로 : delete?no=1111&type=1
let url = "delete"; // 상대경로로 작성
// 주소에 작성된 쿼리스트링에서 필요한 파라미터만 얻어와서 사용
// 1) 쿼리스트링에 존재하는 파라미터 모두 얻어오기
// location.href : 현재 페이지 주소(http://~)
const params = new URL(location.href).searchParams; // 파라미터만 뽑아서 저장
// 자바스크립트 내장 객체
// 2) 원하는 파라미터만 얻어 와 변수에 저장
const no = "?no=" + params.get("no"); // ?no=1111
const type = "&type=" + params.get("type"); // &type=1
// url에 쿼리스트링 추가
url += no + type // delete?no=1111&type=1
if(confirm("정말 삭제하시겠습니까?")){
location.href = url; // get방식으로 url에 요청
}
})
}
})();