고래씌
[Spring] 8-3. 일반 게시판 상세보기, 조회수 증가 본문
1. 일반 게시판 상세보기, 조회수 증가
▶ 게시판목록에서 클릭 시 기능 만들기
▶ boardListView.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<style>
#boardList {text-align:center;}
#boardList>tbody>tr:hover {cursor:pointer;}
#pagingArea {width:fit-content; margin:auto;}
#searchForm {
width:80%;
margin:auto;
}
#searchForm>* {
float:left;
margin:5px;
}
.select {width:20%;}
.text {width:53%;}
.searchBtn {width:20%;}
/* 썸네일 관련 스타일 */
#boardList tr > td:nth-of-type(2){ /* 2번째 td(제목) */
position: relative;
}
.list-thumbnail{
max-width: 50px;
max-height: 30px;
position: absolute;
left : -15px;
top : 10px;
}
</style>
</head>
<body>
<jsp:include page="/WEB-INF/views/common/header.jsp" />
<div class="content">
<br><br>
<div class="innerOuter" style="padding:5% 10%;">
<h2>
<c:forEach items="${boardTypeList}" var="bt">
<c:if test="${bt.boardCd eq boardCode}">
${bt.boardName}
</c:if>
</c:forEach>
</h2>
<!-- <h2>일반게시판</h2> -->
<br><br>
<c:if test="${not empty loginUser}"> <!-- 로그인 유저가 비어있지 않으면 -->
<a class="btn btn-secondary" style="float:right"
href="${contextPath }/board/insert/${boardCode}">
<!-- 절대경로방식 -->
<!-- 상대경로방식은 ../insert/${boardCode} -->
글쓰기
</a>
</c:if>
<br>
<table id="boardList" class="table table-hover" align="center">
<thead>
<tr>
<th>글번호</th>
<th>제목</th>
<th>작성자</th>
<th>조회수</th>
<th>작성일</th>
</tr>
</thead>
<tbody>
<c:choose>
<c:when test="${empty list }"> <!-- 게시글이 없다면 -->
<tr>
<td colspan="5">게시글이 없습니다.</td>
</tr>
</c:when>
<c:otherwise>
<c:forEach var="board" items="${list }">
<tr onclick="movePage(${board.boardNo})">
<td>${board.boardNo }</td>
<td>${board.boardTitle }</td>
<td>${board.boardWriter }</td>
<td>${board.count}</td>
<td>${board.createDate }</td>
</tr>
</c:forEach>
</c:otherwise>
</c:choose>
</tbody>
</table>
<script>
function movePage(bno) {
location.href = "${contextPath}/board/detail/${boardCode}/" + bno
}
</script>
<br>
<!-- 검색기능 -->
<!-- param.condition값이 비어있지않다면 -->
<c:if test="${not empty param.condition}">
<c:set var="sUrl" value="&condition=${param.condition}&keyword=${param.keyword}" />
</c:if>
<!-- 페이징 -->
<c:set var="url" value="${boardCode}?currentPage="/>
<div id="pagingArea">
<ul class="pagination">
<%-- <c:if test="${pi.currentPage eq 1}" > <!-- currentPage가 1과 같을때 --> --%>
<!-- <li class="page-item"> -->
<!-- 절대경로 방식 -->
<%-- <a href="${contextPath}/board/list/${boardCode}?currentPage=${pi.currentPage -1}" class="page-link">Previous</a> --%>
<!-- 상대경로 방식 -->
<!-- <a class="page-link">Previous</a> -->
<!-- </li> -->
<%-- </c:if> --%>
<c:if test="${pi.currentPage ne 1}">
<li class="page-item">
<!-- 절대경로 방식 -->
<%-- <a href="${contextPath}/board/list/${boardCode}?currentPage=${pi.currentPage -1}" class="page-link">Previous</a> --%>
<!-- 상대경로 방식 -->
<a href="${url}${pi.currentPage -1}${sUrl}" class="page-link">Previous</a>
</li>
</c:if>
<c:forEach var="p" begin="${pi.startPage }" end="${pi.endPage }">
<li class="page-item">
<a href="${url}${p}${sUrl}" class="page-link">${p}</a>
</li>
</c:forEach>
<%-- <c:if test="${pi.currentPage eq pi.maxPage }"> --%>
<!-- <li class="page-item"> -->
<!-- <a class="page-link">NEXT</a> -->
<!-- </li> -->
<%-- </c:if> --%>
<c:if test="${pi.currentPage ne pi.maxPage }" >
<li class="page-item">
<a href="${url}${pi.currentPage +1}${sUrl}" class="page-link">NEXT</a>
<%-- <a href="${boardCode}?currentPage=${pi.currentPage +1}" class="page-link">NEXT</a> --%>
</li>
</c:if>
</ul>
</div>
<!-- 검색기능 -->
<br clear="both"> <!-- float 속성 해제 -->
<form id="searchForm" method="get" align="center" action="${boardCode}" >
<div class="select">
<select class="custom-select" name="condition">
<!-- writer와 같다면 selected 값이 반환이 되고 아니면 빈값이 반환되도록 설정 -->
<option value="writer" ${param.condition eq 'writer' ? 'selected' : '' }>작성자</option>
<option value="title" ${param.condition eq 'title' ? 'selected' : '' }>제목</option>
<option value="content" ${param.condition eq 'content' ? 'selected' : '' }>내용</option>
<option value="titleAndContent" ${param.condition eq 'titleAndContent' ? 'selected' : '' }>제목+내용</option>
</select>
</div>
<div class="text">
<input type="text" class="form-control" name="keyword" value="${param.keyword}"/>
</div>
<button type="submit" class="searchBtn btn btn-secondary">검색</button>
</form>
</div>
</div>
<jsp:include page="/WEB-INF/views/common/footer.jsp" />
</body>
</html>
▶ boardDetailView.jsp

=> core와 functions 추가
<%@ 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>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<style>
img{
width: 400px;
}
</style>
</head>
<body>
<jsp:include page="/WEB-INF/views/common/header.jsp"></jsp:include>
<div class="content">
<br><br>
<div class="innerOuter">
<h2>게시글 상세보기</h2>
<br>
<table id="contentArea" align="center" class="table">
<tr>
<th width="100">제목</th>
<td colspan="3">${board.boardTitle}</td>
</tr>
<tr>
<th>작성자</th>
<td>
${board.boardWriter}
</td>
<th>작성일</th>
<td>${board.createDate}</td>
</tr>
<c:if test="${board.originName ne null}"> <!-- 첨부파일이 없다면 -->
<tr>
<th>첨부파일</th>
<td>
<button type="button" class="btn btn-outline-success btn-block">
${board.originName} - 다운로드
</button>
</td>
</tr>
</c:if>
<tr>
<th>내용</th>
<td></td>
<th>조회수</th>
<td>${board.count}</td>
</tr>
<tr>
<td colspan="4">
<p style="height:150px;">
${board.boardContent}
</p>
</td>
</tr>
</table>
<br>
<div align="center">
<!-- 현재 게시글을 작성한 본인인 경우에만 수정하기 버튼이 보여야함 -->
<c:if test="${loginUser.userNo eq userNo}">
<a class="btn btn-primary" href="${contextPath}/board/update/${boardCode}/${board.boardNo}">수정하기</a>
</c:if>
</div>
<br><br>
<!-- 댓글 -->
<table id="replyArea" class="table" align="center">
<thead>
<tr>
<th colspan="2">
<textarea class="form-control" name="replyContent" id="replyContent" rows="2" cols="55"
style="resize:none; width:100%;"></textarea>
</th>
<th style="vertical-align:middle;">
<!-- 비동기 요청으로 댓글 등록 할 예정 -->
<button class="btn btn-secondary" onclick="insertReply();">등록하기</button>
</th>
</tr>
<tr>
<td colspan="3">댓글(<span id="rcount"></span>)</td>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
<jsp:include page="/WEB-INF/views/common/footer.jsp"></jsp:include>
</body>
</html>
- 게시판 상세보기, 조회수 증가
▶ BoardController.java
=> 조회수 증가 서비스는 F5를 눌려서 조회수증가가 안되도록 쿠키를 활용해서 중복으로 증가되지 않도록 방지할 예정.
=> 본인의 글은 애초에 조회수가 증가되지 않게끔 설정
=> 쿠키값을 이용하기 위해서 HttpServletRequeset와 HttpServletResponse, HttpSession이 필요함
@GetMapping("/deatail/{boardCode}/{boardNo}")
public String selectBoard(
@PathVariable("boardCode") String boardCode,
@PathVariable("boardNo") int boardNo,
Model model,
HttpServletRequest req,
HttpServletResponse res,
HttpSession session
) {
// 게시판 정보 조회
Board b = boardService.selectBoard(boardNo);
// 상세조회 성공시 쿠키를 활용해서 조회수가 중복으로 증가되지 않도록 방지
// + 본인의 글은 애초에 조회수가 증가되지 않게끔 설정
if(b != null) {
int userNo = 0;
Member loginUser = (Member) session.getAttribute("loginUser");
if(loginUser != null) {
userNo = loginUser.getUserNo();
}
// 게시글 작성자의 boardWriter와 세션에서 얻어온 userNo가 같지 않은 경우에만 조회수 증가
if(Integer.parseInt(b.getBoardWriter()) != userNo) {
// 쿠키
Cookie cookie = null;
Cookie[] cArr = req.getCookies(); // 사용자의 쿠키정보를 가져오기
if(cArr != null & cArr.length > 0) {
for(Cookie c : cArr) {
// 사용자의 쿠키 목록중, 쿠키의 이름이 "readBoardNo"라는 것을 찾을예정
if("readBoardNo".equals(c.getName())) {
cookie = c;
break;
}
}
}
int result = 0;
// readBoardNo라는 이름의 쿠키가 생성된적이 없을때
if(cookie == null) {
// readBoardNo쿠키 생성
cookie = new Cookie("readBoardNo", boardNo +"");
// 조회수 증가 서비스 호출
result = boardService.increaseCount(boardNo);
}else {
// readBoardNo라는 이름의 쿠키가 존재하는 케이스
// 기존 쿠키값중에 중복되는 게시글번호가 없는 경우,
// 조회수증가와 함께 쿠키에 저장된 값중 현재 조회된 게시글번호를 추가
String[] arr = cookie.getValue().split("/"); // 1/2/5/11 .... => [1,2,5,11...]
// 배열을 컬렉션(List)으로 변환 → indexOf를 사용할 예정
// List.indexOf(obj) : 컬렉션안에서 매개변수로 들어온 obj와 일치하는 부분의 인덱스를 반환해줌
// 일치하는 값이 없다면? -1 반환
List<String> list = Arrays.asList(arr);
// 기존의 쿠키값들중 현재 게시글번호와 일치하는값이 없는 경우 => 처음 보는 게시글인 경우
if(list.indexOf(boardNo+"") == -1) {
// 조회수 증가
result = boardService.increaseCount(boardNo);
// 쿠키값에 현재 게시글번호 추가
cookie.setValue(cookie.getValue() + "/" + boardNo);
}
}
if(result > 0) { // 성공적으로 조회수 증가시
b.setCount(b.getCount() + 1);
cookie.setPath(req.getContextPath());
cookie.setMaxAge(1 * 60 * 60); // 1시간만 유지되도록 설정. 초단위로 작성해주어야함
res.addCookie(cookie);
}
}
}else { // 조회된 board가 null인 경우
model.addAttribute("erroMsg", "게시글 조회 실패....");
return "common/errorPage";
}
model.addAttribute("board", b);
return "board/boardDetailView";
}
1) 일반 게시글 상세보기
▶BoardService.java
Board selectBoard(int boardNo);
▶ BoardServiceImpl.java
@Override
public Board selectBoard(int boardNo) {
return boardDao.selectBoard(boardNo);
}
▶ BoardDao.java
Board selectBoard(int boardNo);
▶ BoardDaoImpl.java
@Override
public Board selectBoard(int boardNo) {
return sqlSession.selectOne("boardMapper.selectBoard", boardNo);
}
▶ board-maaper.xml
<!-- 일반게시판 상세보기 -->
<select id="selectBoard" parameterType="int" resultType="board">
SELECT
BOARD_NO,
BOARD_WRITER,
BOARD_TITLE,
BOARD_CONTENT,
BOARD_CONTENT,
COUNT,
CREATE_DATE
FROM BOARD
WHERE BOARD_NO = #{boardNo}
AND STATUS = 'Y'
</select>
2) 일반게시판 조회수 증가
▶BoardService.java
int increaseCount(int boardNo);
▶ BoardServiceImpl.java
@Override
public int increaseCount(int boardNo) {
return boardDao.increaseCount(boardNo);
}
▶ BoardDao.java
int increaseCount(int boardNo);
▶ BoardDaoImpl.java
@Override
public int increaseCount(int boardNo) {
return sqlSession.update("boardMapper.increaseCount", boardNo);
}
▶ board-maaper.xml
<!-- 일반 게시판 조회수 -->
<update id="increaseCount">
UPDATE BOARD
SET COUNT = COUNT + 1
WHERE BOARD_NO = #{boardNO}
</update>
▶ 결과

2-1. 버전 1) 사진게시판일때 사진리스트 불러오기
▶ boardDetailView.jsp

=> 사진게시판 일때, 사진 목록이 보이고, 다운로드 할 수 있도록 코드 추가
▶ BoardController.java
위에 작성했던 @GetMapping("/detail/{boardCode}/{boardNo}")안에 아래 코드를 추가함

▶ BoardService.java
List<BoardImg> selectBoardImgList(int boardNo);
▶ BoardServiceImpl.java
@Override
public List<BoardImg> selectBoardImgList(int boardNo) {
return boardDao.selectBoardImgList(boardNo);
}
▶ BoardDao.java
List<BoardImg> selectBoardImgList(int boardNo);
▶ BoardDaoImpl.java
@Override
public List<BoardImg> selectBoardImgList(int boardNo) {
return sqlSession.selectList("boardMapper.selectBoardImgList", boardNo);
}
▶ board-mapper.xml
<!-- 사진게시판 사진목록 조회 -->
<select id="selectBoardImgList" parameterType="int" resultType="boardImg">
SELECT *
FROM BOARD_IMG
WHERE REF_BNO = #{boardNo}
ORDER BY IMG_LEVEL
</select>
▶ 결과

=> 하지만 이 방법은 객체지향 방법이 아니다!!! 자바는 객체지향언어이고 DB는 SQL 데이터 중심언어이기 때문에 충돌이 일어날 수 있다.
=> 이방법을 해결하기 위해서 아래의 방법으로도 해보겠다!
2-2. 버전 2) 사진게시판 사진목록 불러오기 (BoardExt 클래스 추가)
▶ BoardExt.java
package com.kh.spring.board.model.vo;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@NoArgsConstructor
@AllArgsConstructor
@Data
public class BoardExt extends Board {
private List<BoardImg> imgList;
// 댓글목록
// 유저정보
}
▶ BoardController.java

=> 사진게시판의 사진목록을 조회할 때 추가하였던 두줄 코드를 모두 주석처리하고, Board b = boardService.selectBoard(boardNo) 를 BoardExt b = ~ 로 변경
▶ BoardService.java, BoardServiceImpl.java, BoardDao.java, BoardDaoImpl.java 모두 Board → BoardExt 로 변경




▶ board-mapper.xml 쿼리문 수정

=> IMG_ORIGIN_NAME 과 IMG_CHANGE_NAME, ORIGIN_NAME, BOARD_CD, CHANGE_NAME, BOARD_IMG_NO, IMG_LEVEL 을 추가하고 JOIN절 추가
=> resultMap 2개 추가
<resultMap type="boardExt" id="boardExtResultMap">
<id column="BOARD_NO" property="boardNo"/>
<result column="BOARD_TITLE" property="boardTitle"/>
<result column="BOARD_CONTENT" property="boardContent"/>
<result column="CREATE_DATE" property="createDate" />
<result column="BOARD_WRITER" property="boardWriter"/>
<result column="COUNT" property="count"/>
<result column="ORIGIN_NAME" property="originName" />
<result column="CHANGE_NAME" property="changeName" />
<result column="BOARD_CD" property="boardCd" />
<collection property="imgList" resultMap="boardImgResultMap">
</collection>
</resultMap>
<resultMap id="boardImgResultMap" type="boardImg">
<id column="BOARD_IMG_NO" property="boardImgNo" />
<result column="IMG_ORIGIN_NAME" property="originName"/>
<result column="IMG_CHANGE_NAME" property="changeName"/>
<result column="IMG_LEVEL" property="imgLevel" />
</resultMap>
▶ boardDetailView.jsp

=> board.imgList 추가
=> not empty imgList
▶ 결과

=> 오로지 게시글 상세조회만 되도록 하는 쿼리문을 만들고 싶다면...?
▶ board-mapper.xml
<!-- 일반게시판 resultMap -->
<resultMap type="boardExt" id="boardResultSet">
<id column="BOARD_NO" property="boardNo"/>
<result column="BOARD_TITLE" property="boardTitle"/>
<result column="BOARD_CONTENT" property="boardContent"/>
<result column="CREATE_DATE" property="createDate" />
<result column="BOARD_WRITER" property="boardWriter"/>
<result column="COUNT" property="count"/>
<result column="USER_NAME" property="userName" />
<!-- VO 클래스를 매핑할때 필요한 태그(association) -->
<!-- <association property="user" resultMap="memberResultSet" /> -->
<!--
collection ofType : 제네릭
javaType : 자바자료형 → List<BoardImg>
property : 생성된 데이터를 저장할 boardExt의 필드명
select : 실행시킬 select태그의 아이디값
column : select에 지정한 아이디값의 쿼리문 호출시 필요한 매개변수
-->
<collection property="imgList" ofType="boardImg" javaType="java.util.List"
select="selectBoardImgList" column="BOARD_NO" />
</resultMap>
<!-- ofType : List<BoardImg>형식으로 만들어줌 -->
<!-- 결과값을 컬렉션으로 받아오고 이미지 BoardImg 에 넣어줌 -->
<!-- 일반게시판만 불러오기 -->
<select id="selectBoardOnly" parameterType="int" resultMap="boardResultSet">
SELECT
BOARD_NO,
BOARD_WRITER,
BOARD_TITLE,
BOARD_CONTENT,
COUNT,
CREATE_DATE,
USER_NAME
FROM BOARD
LEFT JOIN MEMBER ON BOARD_WRITER = USER_NO
WHERE BOARD_NO = #{boardNo}
AND BOARD.STATUS = 'Y'
</select>
▶ BoardDaoImpl.java

▶ BoardExt.java
=> 작성자에 유저이름이 뜨게 하기 위해서 userName 추가

▶ boardDetailView.jsp

=> ${board.userName}으로 수정
=> 똑같이 정상실행되는 것을 볼 수 있다.

'Server > Spring' 카테고리의 다른 글
| [Spring] 8-5. 게시판 수정하기(+ 사진삭제, 사진수정, 사진추가) (0) | 2024.01.26 |
|---|---|
| [Spring] 8-4. 첨부파일 사진 다운로드 (0) | 2024.01.25 |
| [Spring] 8-2. 사진게시판(사진 여러개일 때(다중 insert문)) (0) | 2024.01.24 |
| [Spring] 8-1. 일반 게시글 등록(크로스사이트스크립트 공격 방지) (0) | 2024.01.24 |
| [Spring] 7. Application 전역에 있는 데이터를 저장(Intercepter) (0) | 2024.01.24 |