[B-3-2] 댓글과 댓글 수에 대한 처리

2020. 1. 19. 21:09Project B (SPMS)/Project B 파트8

반응형

SQL 실시 1

게시글에 대한 댓글을 추가하면, TBL_REPLY 테이블에 삽입하고,

TBL_BOARD 테이블에는 댓글의 수인 replyCnt 라는 컬럼추가해서 해당 게시물 댓글 수를 갱신한다.

 

SQL 실시 2

기존에 댓글이 존재하는 상황이라면, replyCnt에 반영해놓아야하므로 SQL 실시 2 쿼리를 실행한다.

 

SQL 실시 1

alter table tbl_board
add (replycnt number default 0);

SQL 실시 2

update tbl_board
set replycnt = (select count(rno)
                from tbl_reply
                where tbl_reply.bno = tbl_board.bno
                );

 

 

 


src/main/java

com.spms.domain

BoardVO.java

더보기
package com.spms.domain;

import java.util.Date;

import lombok.Data;

@Data
public class BoardVO {
	private Long bno;
	private String title;
	private String content;
	private String writer;
	private Date regdate;
	private Date updateDate; 
	private int replyCnt; // 댓글 숫자
}

댓글 수를 의미하는 인스턴스 변수 replyCnt 추가


src/main/java

com.spms.mapper

BoardMapper.java 인터페이스

 

더보기
package com.spms.mapper;

import java.util.List;

import org.apache.ibatis.annotations.Param;

import com.spms.domain.BoardVO;
import com.spms.domain.Criteria;

public interface BoardMapper {
	public List<BoardVO> getListWithPaging(Criteria cri);
	public Long insertSelectKey(BoardVO board);
	public BoardVO read(Long bno);
	public int update(BoardVO board);
	public int delete(Long bno);
	public int getTotalCount(Criteria cri);
	public void updateReplyCnt(@Param("bno") Long bno, @Param("amount") int amount); // 댓글 카운트 갱신
}

 

새로 추가된 updateReplyCnt() 는 해당 게시물의 번호인  bno, 증가 및 감소를 의미하는 amount 변수에 파라미터를 받을 수 있게 처리한다.

즉, 댓글이 등록되면, 1이 증가하고, 

삭제되면, 1이 감소한다.

기본적으로 하나의 파라미터 타입을 사용하기때문에 2개 이상의 데이터를 전달하려면 @Param 어노테이션을 이용해 처리한다.


src/main/resources

com.spms.mapper

BoardMapper.xml 수정

 

더보기
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.spms.mapper.BoardMapper">

	<sql id="criteria">
		<trim prefix="(" suffix=") AND " prefixOverrides="OR">
			<foreach item='type' collection="typeArr">
				<trim prefix="OR">
					<choose>
						<when test="type == 'T'.toString()">
							TITLE like '%'||#{keyword}||'%'
						</when>
						<when test="type == 'C'.toString()">
							CONTENT like '%'||#{keyword}||'%'
						</when>
						<when test="type == 'W'.toString()">
							WRITER like '%'||#{keyword}||'%'
						</when>
					</choose>
				</trim>
			</foreach>
		</trim>
	</sql>

	<!-- 페이징 목록 조회 -->
	<select id="getListWithPaging" resultType="com.spms.domain.BoardVO">
	<![CDATA[
		select 
			BNO, TITLE, "CONTENT", WRITER, REGDATE, UPDATEDATE, REPLYCNT
		from 
		(
			select /*+INDEX_DESC(TBL_BOARD PK_BOARD) */
				rownum RNUM, BNO, TITLE, "CONTENT", WRITER, REGDATE, UPDATEDATE, REPLYCNT
			from 
				TBL_BOARD
      		where 
	]]>
		<include refid="criteria"></include>
	<![CDATA[
			rownum <= #{pageNum} * #{amount}
		)
		where RNUM > (#{pageNum} -1) * #{amount}   
	]]>
	</select>
	
	<!-- 전체 레코드 구하기 -->
	<select id="getTotalCount" resultType="int">
		select count(*)
		from TBL_BOARD
		where 
		<include refid="criteria"></include>
		bno > 0
	</select>
	
	<!-- 등록 (선택키 활용) -->
	<insert id="insertSelectKey">
		<selectKey keyProperty="bno" order="BEFORE" resultType="long">
			select SEQ_BOARD.nextval 
			from DUAL
		</selectKey>
		insert into TBL_BOARD (
			BNO, TITLE, CONTENT, WRITER
		)
		values (
			#{bno}, #{title}, #{content}, #{writer}
		)
	</insert>
	
	<!-- 조회 -->
	<select id="read" resultType="com.spms.domain.BoardVO">
		select * 
		from TBL_BOARD 
		where BNO = #{bno}
	</select>
	
	<!-- 수정 -->
	<update id="update">
		update TBL_BOARD set 
				TITLE = #{title},
				CONTENT = #{content},
				WRITER = #{writer},
				UPDATEDATE = SYSDATE
		where BNO = #{bno}
	</update>
	
	<!-- 삭제 -->
	<delete id="delete">
		delete TBL_BOARD
		where BNO = #{bno}
	</delete>
	
	<!-- updateReplyCnt() 에 대한 정의 기술 -->
	<update id="updateReplyCnt">
		update TBL_BOARD
		set replycnt = replycnt + #{amount}
		where BNO = #{bno}
	</update>
	
</mapper>

게시물의 목록을 처리하는 부분에서 새로 추가된 replyCnt 컬럼을 갖고오도록 인라인뷰 내에서 추가하고,

페이징 처리 리스트(select) 조회에도 추가한다.

 


src/main/java

com.spms.service

ReplyServiceImpl.java

더보기
package com.spms.service;

import java.util.List;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.spms.domain.Criteria;
import com.spms.domain.ReplyPageDTO;
import com.spms.domain.ReplyVO;
import com.spms.mapper.BoardMapper;
import com.spms.mapper.ReplyMapper;

import lombok.AllArgsConstructor;
import lombok.extern.log4j.Log4j;

@Service
@Log4j
@AllArgsConstructor
public class ReplyServiceImpl implements ReplyService {

	private ReplyMapper replyMapper;
	private BoardMapper boardMapper;
	
	@Transactional
	@Override
	public int register(ReplyVO vo) {
		log.info("register......" + vo);
		boardMapper.updateReplyCnt(vo.getBno(), 1);
		return replyMapper.insert(vo);
	}

	@Override
	public ReplyVO get(Long rno) {
		log.info("get......" + rno);
		return replyMapper.read(rno);
	}

	@Override
	public int modify(ReplyVO vo) {
		log.info("modify......" + vo);
		return replyMapper.update(vo);
	}
	
	@Transactional
	@Override
	public int remove(Long rno) {
		log.info("remove...." + rno);
		ReplyVO reply = replyMapper.read(rno);
		boardMapper.updateReplyCnt(reply.getBno(), -1);
		return replyMapper.delete(rno);
	}

	@Override
	public List<ReplyVO> getList(Criteria cri, Long bno) {
		log.info("get Reply List of a Board " + bno);
		return replyMapper.getListWithPaging(cri, bno);
	}

	@Override
	public ReplyPageDTO getListPage(Criteria cri, Long bno) {
		return new ReplyPageDTO(replyMapper.getCountByBno(bno), replyMapper.getListWithPaging(cri, bno));
	}
}

기존에는 한가지 매퍼만 이용했지만, 반정규화 처리가 되면서 BoardMapper를 같이 이용해야하는 상황이 됐다.

ReplyServiceImpl에서 새 댓글이 추가되거나 삭제되는 상황이 되면 BoardMapper와 ReplyMapper를 같이 이용해서 처리하고, 이 작업은 트랜잭션으로 처리되어야 한다.

 

또한 댓글 등록 및 댓글 삭제를 담당하는 메서드는 @Transactional 처리가 필요하다.


views/board/

list.jsp

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

<%@ include file="../includes/header.jsp"%>
<div class="row">
	<div class="col-lg-12">
		<h1 class="page-header">게시판 목록</h1>
	</div>
	<!-- /.col-lg-12 -->
</div>
<!-- /.row -->
<div class="row">
	<div class="col-lg-12">
		<div class="panel panel-default">
			<div class="panel-heading">게시글 목록 페이지<button id='regBtn' type="button" class="btn btn-xs pull-right btn-primary">게시글 등록</button></div>
			<!-- /.panel-heading -->
			<div class="panel-body">
				<table class="table table-striped table-bordered table-hover">
					<tbody>
					<c:forEach items="${list }" var="board">
						<tr>
							<td>
								<font color="Gray">
									<c:out value="${board.bno}"/>&nbsp;/&nbsp;
									<c:out value="${board.writer}"/>&nbsp;/&nbsp;
									<fmt:formatDate value="${board.regdate}" pattern="yyyy-MM-dd"/>&nbsp;/&nbsp;
									<fmt:formatDate value="${board.updateDate}" pattern="yyyy-MM-dd"/>
								</font><br />
								<font size="3px">
									<a class='move' href="<c:out value="${board.bno}"/>">
										<c:out value="${board.title }"/> <b>[<c:out value="${board.replyCnt}" />]</b>
									</a>
								</font>
							</td>
						</tr>
					</c:forEach>
					</tbody>
				</table>
				
				<!-- 검색 조건 처리 시작 -->
				<div class='row'>
					<div class="col-lg-12">

						<form id='searchForm' action="/board/list" method='get'>
							<select name='type'>
								<option value=""
									<c:out value="${pageMaker.cri.type == null?'selected':''}"/>>--</option>
								<option value="T"
									<c:out value="${pageMaker.cri.type eq 'T'?'selected':''}"/>>제목</option>
								<option value="C"
									<c:out value="${pageMaker.cri.type eq 'C'?'selected':''}"/>>내용</option>
								<option value="W"
									<c:out value="${pageMaker.cri.type eq 'W'?'selected':''}"/>>작성자</option>
								<option value="TC"
									<c:out value="${pageMaker.cri.type eq 'TC'?'selected':''}"/>>제목
									or 내용</option>
								<option value="TW"
									<c:out value="${pageMaker.cri.type eq 'TW'?'selected':''}"/>>제목
									or 작성자</option>
								<option value="TWC"
									<c:out value="${pageMaker.cri.type eq 'TWC'?'selected':''}"/>>제목
									or 내용 or 작성자</option>
							</select> 
							<input type='text' name='keyword' value='<c:out value="${pageMaker.cri.keyword}"/>' />
							<input type='hidden' name='pageNum'	value='<c:out value="${pageMaker.cri.pageNum}"/>' />
							<input type='hidden' name='amount' value='<c:out value="${pageMaker.cri.amount}"/>' />
							<button class='btn btn-default'>Search</button>
						</form>
					</div>
				</div>
				<!-- 검색 조건 처리 끝 -->
				
				<!-- 페이지네이션 시작 -->
				<div class='pull-right'>
					<ul class="pagination">
						<!-- 이전 -->
						<c:if test="${pageMaker.prev}">
							<li class="paginate_button previous"><a
								href="${pageMaker.startPage -1}">Previous</a></li>
						</c:if>

						<!-- 현재 -->
						<c:forEach var="num" begin="${pageMaker.startPage}" end="${pageMaker.endPage}">
							<li class='paginate_button  ${pageMaker.cri.pageNum == num ? "active":""} '>
								<a href="${num}">${num}</a>
							</li>
						</c:forEach>
						
						<!-- 다음 -->
						<c:if test="${pageMaker.next}">
							<li class="paginate_button next"><a
								href="${pageMaker.endPage +1 }">Next</a></li>
						</c:if>
					</ul>
				</div>
				<!--  페이지네이션 끝 -->
				
			<!-- 이전 현재 다음 버튼의 a 태그동작을 막기위한 폼 태그 -->
			<form id='actionForm' action="/board/list" method='get'>
				<input type='hidden' name='pageNum' value='${pageMaker.cri.pageNum}'>
				<input type='hidden' name='amount' value='${pageMaker.cri.amount}'>
			</form>
			<!-- 폼 태그 종료 -->
			
			<!-- Modal  추가 -->
			<div class="modal fade" id="myModal" tabindex="-1" role="dialog"
				aria-labelledby="myModalLabel" aria-hidden="true">
				<div class="modal-dialog">
					<div class="modal-content">
						<div class="modal-header">
							<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
							<h4 class="modal-title" id="myModalLabel">게시글 처리 결과</h4>
						</div>
						<div class="modal-body">처리가 완료되었습니다.</div>
						<div class="modal-footer">
							<button type="button" class="btn btn-default"
								data-dismiss="modal">Close</button>
							<button type="button" class="btn btn-primary">Save changes</button>
						</div>
					</div>
					<!-- /.modal-content -->
				</div>
				<!-- /.modal-dialog -->
				
				
			</div>
			<!-- /.panel-body -->
		</div>
		<!-- /.panel -->
	</div>
	<!-- /.col-lg-12 -->
</div>
<!-- /.row -->
</div>
<!-- /#page-wrapper -->
<%@ include file="../includes/footer.jsp"%>

<script type="text/javascript">
	$().ready(function() {

						var result = '<c:out value="${result}"/>';

						checkModal(result);

						history.replaceState({}, null, null);

						function checkModal(result) {

							if (result === '' || history.state) {
								return;
							}

							if (parseInt(result) > 0) {
								$(".modal-body").html(
										"게시글 " + parseInt(result) + " 번이 등록되었습니다."
								);
							}

							$("#myModal").modal("show");
						}

						$("#regBtn").on("click", function() {

							self.location = "/board/register";

						});
						
						var actionForm = $("#actionForm");

						$(".paginate_button a").on("click", function(e) {
							e.preventDefault();
							console.log('click');
							actionForm.find("input[name='pageNum']").val($(this).attr("href"));
							actionForm.submit();
						});
						

						$(".move").on("click",function(e) {
											e.preventDefault();
											actionForm.append("<input type='hidden' name='bno' value='"
															+ $(this).attr("href")
															+ "'>");
											actionForm.attr("action","/board/get");
											actionForm.submit();

						});
						
						var searchForm = $("#searchForm");
						
						/* 검색 버튼의 이벤트 처리 시작 */
						$("#searchForm button").on("click", function(e) {

									if (!searchForm.find("option:selected").val()) {
										alert("검색종류를 선택하세요");
										return false;
									}

									if (!searchForm.find("input[name='keyword']").val()) {
										alert("키워드를 입력하세요");
										return false;
									}

									searchForm.find("input[name='pageNum']").val("1");
									e.preventDefault();

									searchForm.submit();

						});
						/* 검색 버튼의 이벤트 처리 종료  */
						
						
					});
</script>

게시물의 목록 화면에서 댓글의 숫자가 출력될 수 있도록 수정한다.

 


댓글 카운트 테스트 결과

댓글 카운트 테스트 결과

 

 

반응형

'Project B (SPMS) > Project B 파트8' 카테고리의 다른 글

[B-3-1] 회원가입  (0) 2020.01.19