2020. 1. 19. 21:09ㆍProject 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}"/> /
<c:out value="${board.writer}"/> /
<fmt:formatDate value="${board.regdate}" pattern="yyyy-MM-dd"/> /
<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">×</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 |
---|