커뮤니티 - 게시글 상세 조회 기능 구현 (23.07.19)

·2023년 7월 19일
0

Server

목록 보기
23/35
post-thumbnail

📝 게시글 상세 조회 기능


💡 ERD


💡 Oracle

-- 게시판 이미지 테이블 생성
CREATE TABLE BOARD_IMG(
    IMG_NO NUMBER PRIMARY KEY,
    IMG_RENAME VARCHAR2(500) NOT NULL,
    IMG_ORIGINAL VARCHAR2(500) NOT NULL,
    IMG_LEVEL NUMBER NOT NULL,
    BOARD_NO NUMBER REFERENCES BOARD
);

COMMENT ON COLUMN BOARD_IMG.IMG_NO IS '이미지번호';
COMMENT ON COLUMN BOARD_IMG.IMG_RENAME IS '이미지 저장 경로 + 변경명';
COMMENT ON COLUMN BOARD_IMG.IMG_ORIGINAL IS '이미지 원본명';
COMMENT ON COLUMN BOARD_IMG.IMG_LEVEL IS '이미지 위치 지정 번호';
COMMENT ON COLUMN BOARD_IMG.BOARD_NO IS '게시글 번호';

-- 이미지 번호 시퀀스
CREATE SEQUENCE SEQ_IMG_NO NOCACHE;

-- 샘플 데이터
INSERT INTO BOARD_IMG VALUES(
    SEQ_IMG_NO.NEXTVAL, '/resources/images/board/sample1.jpg', 'cat1.jpg', 0, 500
);

INSERT INTO BOARD_IMG VALUES(
    SEQ_IMG_NO.NEXTVAL, '/resources/images/board/sample2.jpg', 'cat2.jpg', 1, 500
);

INSERT INTO BOARD_IMG VALUES(
    SEQ_IMG_NO.NEXTVAL, '/resources/images/board/sample3.jpg', 'cat3.jpg', 2, 500
);

INSERT INTO BOARD_IMG VALUES(
    SEQ_IMG_NO.NEXTVAL, '/resources/images/board/sample4.jpg', 'cat4.jpg', 3, 500
);

INSERT INTO BOARD_IMG VALUES(
    SEQ_IMG_NO.NEXTVAL, '/resources/images/board/sample5.jpg', 'cat5.jpg', 4, 500
);

COMMIT;

-- 게시글 상세 조회
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 = 500
AND BOARD_ST = 'N';

-- 특정 게시글에 첨부된 이미지 목록 조회
SELECT * FROM BOARD_IMG
WHERE BOARD_NO = 500
ORDER BY IMG_LEVEL;

💡 VS Code

🔎 boardDetail-style.css

/* 상세조회 전체 영역 */
.board-detail{
    width: 1000px;
    min-height: 700px;
    border: 1px solid #ccc;
    margin: 50px auto;
    padding: 20px;
}

/* 게시글 제목 */
.board-title{
    margin: 0;
    padding: 20px 0;
    font-size: 2em;
    border-bottom: 3px solid #ccc;
}

.board-title > span{
    font-size: 16px;
    color: gray;
}

/* 게시글 헤더 */
.board-header{
    padding: 20px 0;
    border-bottom: 3px solid #ccc;
    display: flex;
    justify-content: space-between;
}

/* 작성자 영역 */
.board-writer{
    display: flex;
    align-items: center;
}

/* 프로필 */
.board-writer > img{
    width: 40px;
    height: 40px;
}

/* 닉네임 */
.board-writer > span{
    font-weight: bold;
    margin-left: 10px;
}

/* 게시글 정보 */
.board-info{
    width: 320px;
    font-size: 0.8em;
}

.board-info > p{
    margin: 0;
}

.board-info span{
    width: 120px;
    display: inline-block;
    font-weight: bold;
}

.img-box{
    display: flex;
    justify-content: space-between;
}

/* 이미지를 감싸는 div */
.boardImg{
    width: 230px;
    height: 230px;
    display: inline-block;
    text-align: center;
}

/* 이미지 */
.boardImg img{
    max-width: 90%;
    max-height: 90%;
}

/* 썸네일만 이미지 크게 */
.thumbnail{
    width: 300px;
    height: 300px;
}

/* 다운로드 a태그 */
.boardImg a{
    display: block;
    text-align: center;
    color: black;
    text-decoration: none;
    font-size: 12px;
    margin-top: 5px;
}

/* 내용 */
.board-content{
    padding: 30px 0;
    margin: 30px 0;
    border-top: 3px solid #ccc;
    border-bottom: 3px solid #ccc;
    font-size: 18px;
}

/* 버튼 영역 */
.board-btn-area{
    text-align: right;
}

/* 버튼 */
.board-btn-area button{
    width: 80px;
    height: 30px;

    font-weight: bold;
    border: 0;
    background-color: #455ba8;
    color : white;

    cursor: pointer;
}

.board-btn-area button:hover{
    background-color: white;
    color: #455ba8;
    border: 2px solid #455ba8;
}

🔎 boardDetail.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"  %>

<!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">

    <script src="https://kit.fontawesome.com/4dca1921b4.js" crossorigin="anonymous"></script>
</head>
<body>
    <main>

        <jsp:include page="/WEB-INF/views/common/header.jsp"/>

        ${detail}

        <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}">
                        <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">
                내용입니다<br>
                내용입니다<br>
                내용입니다<br>
                내용입니다<br>
                내용입니다<br>
                내용입니다<br>
            </div>

            <!-- 버튼 영역 -->
            <div class="board-btn-area">

                <c:if test="${loginMember.memberNo == detail.memberNo}">
                    <button id="updateBtn">수정</button>
                    <button id="deleteBtn">삭제</button>
                </c:if>

                <button id="goToListBtn">목록으로</button>
            </div>
            
        </section>
    </main>

	<jsp:include page="/WEB-INF/views/common/footer.jsp"/>		

</body>
</html>

🔎 boardList.jsp

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

<!-- map에 저장된 값을 각각 변수에 저장 -->
<c:set var="boardName" value="${map.boardName}" />
<c:set var="pagination" value="${map.pagination}" />
<c:set var="boardList" value="${map.boardList}" />

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>${boardName}</title>

    <link rel="stylesheet" href="${contextPath}/resources/css/boardList-style.css">

    <link rel="stylesheet" href="${contextPath}/resources/css/main-style.css">

    <script src="https://kit.fontawesome.com/4dca1921b4.js" crossorigin="anonymous"></script>
</head>
<body>
    <main>
		<jsp:include page="/WEB-INF/views/common/header.jsp"/>		

        <section class="board-list">

            <h1 class="board-name">${boardName}</h1>

            <div class="list-wrapper">
                <table class="list-table">
                    <thead>
                        <tr>
                            <th>글번호</th>
                            <th>제목</th>
                            <th>작성자</th>
                            <th>작성일</th>
                            <th>조회수</th>
                        </tr>
                    </thead>

                    <tbody>

                        <c:choose>
                            <c:when test="${empty boardList}">
                                <!-- 게시글 목록 조회 결과가 비어 있다면 -->
                                <tr>
                                    <th colspan="5">게시글이 존재하지 않습니다.</th>
                                </tr>
                            </c:when>

                            <c:otherwise>
                                <!-- 게시글 목록 조회 결과가 비어 있지 않다면 -->

                                <!-- 향상된 for문처럼 사용 -->
                                <c:forEach var="board" items="${boardList}">
                                    <tr>
                                        <td>${board.boardNo}</td>
                                        <td>
                                            <a href="detail?no=${board.boardNo}&cp=${pagination.currentPage}&type=${param.type}">${board.boardTitle}</a>
                                        </td>
                                        <td>${board.memberNickname}</td>
                                        <td>${board.createDate}</td>
                                        <td>${board.readCount}</td>
                                    </tr>
                                </c:forEach>

                            </c:otherwise>

                        </c:choose>

                    </tbody>

                </table>
            </div>

            <div class="btn-area">
                <button id="insertBtn">글쓰기</button>
            </div>
            
            <div class="pagination-area">

                <!-- 페이지네이션 a태그에 사용될 공통 주소를 저장한 변수 선언 -->
                <c:set var="url" value="list?type=${param.type}&cp="/>

                <ul class="pagination">
                    <!-- 첫 페이지로 이동 -->
                    <li><a href="${url}1">&lt;&lt;</a></li>

                    <!-- 이전 목록 마지막 번호로 이동 -->
                    <li><a href="${url}${pagination.prevPage}">&lt;</a></li>

                    <!-- 범위가 정해진 일반 for문을 사용 -->
                    <c:forEach var="i" begin="${pagination.startPage}" end="${pagination.endPage}" step="1">

                        <c:choose>
                            <c:when test="${i == pagination.currentPage}">
                                <li><a class="current">${i}</a></li>
                            </c:when>

                            <c:otherwise>
                                <li><a href="${url}${i}">${i}</a></li>
                            </c:otherwise>
                        </c:choose>

                    </c:forEach>

                    <!-- 다음 목록 시작 번호로 이동 -->
                    <li><a href="${url}${pagination.nextPage}">&gt;</a></li>
                    
                    <!-- 끝 페이지로 이동 -->
                    <li><a href="${url}${pagination.maxPage}">&gt;&gt;</a></li>
                </ul>
            </div>

            <form action="#" method="get" id="boardSearch">

                <select name="key">
                    <option value="t">제목</option>
                    <option value="c">내용</option>
                    <option value="tc">제목+내용</option>
                    <option value="w">작성자</option>
                </select>

                <input type="text" name="query" placeholder="검색어를 입력해 주세요.">

                <button>검색</button>
            </form>

        </section>
    </main>

    <jsp:include page="/WEB-INF/views/common/footer.jsp"/>
</body>
</html>

💡 Eclipse

우선 'webapp/resources/images/board' 경로에 샘플 이미지를 저장해 주었다.

🔎 BoardDetailServlet.java

package edu.kh.community.board.controller;

import java.io.IOException;

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.vo.BoardDetail;

@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);
			
			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();
		}
	
	}
	
}

🔎 BoardService.java

	/** 게시글 상세 조회 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;
	}
	
}

🔎 BoardDAO.java

	/** 게시글 상세 조회 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;
	}

	/** 게시글에 첨부된 이미지 리스트 조회 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;
	}
	
}

🔎 BoardDetail.java

package edu.kh.community.board.model.vo;

import java.util.List;

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

@Getter
@Setter
@ToString
@NoArgsConstructor
public class BoardDetail {
   
   private int boardNo;
   private String boardTitle;
   private String boardContent;
   private String createDate;
   private String updateDate;
   private int readCount;
   private String memberNickname;
   private String profileImage;
   private int memberNo;
   private String boardName;
   
   
   private List<BoardImage> imageList;
   
}

🔎 BoardImage.java

package edu.kh.community.board.model.vo;

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

@Getter
@Setter
@ToString
@NoArgsConstructor
public class BoardImage {

	private int imageNo;
	private String imageReName;
	private String imageOriginal;
	private int imageLevel;
	private int boardNo;
	
}

🔎 board-sql.xml

	<!-- 게시글 상세 조회 -->
	<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>
	
	<!-- 게시글에 첨부된 이미지 리스트 조회 -->
	<entry key="selectImageList">
		SELECT * FROM BOARD_IMG
		WHERE BOARD_NO = ?
		ORDER BY IMG_LEVEL
	</entry>

📌 출력 화면

profile
풀스택 개발자 기록집 📁

1개의 댓글

comment-user-thumbnail
2023년 7월 19일

너무 잘 읽었습니다, 많은 것을 배웠습니다.

답글 달기