고래씌

[Spring] 8-3. 일반 게시판 상세보기, 조회수 증가 본문

Server/Spring

[Spring] 8-3. 일반 게시판 상세보기, 조회수 증가

고래씌 2024. 1. 25. 09:50

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}으로 수정

 

 

=>  똑같이 정상실행되는 것을 볼 수 있다.