-- 게시판 이미지 테이블 생성
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;
/* 상세조회 전체 영역 */
.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;
}
<%@ 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>
<%@ 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"><<</a></li>
<!-- 이전 목록 마지막 번호로 이동 -->
<li><a href="${url}${pagination.prevPage}"><</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}">></a></li>
<!-- 끝 페이지로 이동 -->
<li><a href="${url}${pagination.maxPage}">>></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>
우선 'webapp/resources/images/board' 경로에 샘플 이미지를 저장해 주었다.
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();
}
}
}
/** 게시글 상세 조회 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;
}
/** 게시글에 첨부된 이미지 리스트 조회 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;
}
}
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;
}
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;
}
<!-- 게시글 상세 조회 -->
<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>
너무 잘 읽었습니다, 많은 것을 배웠습니다.