게시판(페이징,블록,답글)
/view 페이징, 블록
/rewrite 답글
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.tis.mapper.BoardMapper">
<!-- refer컬럼은 글그룹번호 원글의 글번호를 인덱스로 하자-->
<insert id="insertBoard" parameterType="Board">
<selectKey keyProperty="idx" keyColumn="idx" order="BEFORE" resultType="int">
select board_seq.nextval from dual
</selectKey>
insert into board values(#{idx},#{name},#{pwd},#{subject},
#{content:VARCHAR},sysdate,0,#{filename:VARCHAR},#{originFilename:VARCHAR},
#{filesize:NUMERIC},#{idx},#{lev},#{sunbun})
</insert>
<select id="getTotalCount" resultType="int">
select count(*) from board
</select>
<select id="selectBoardAll" parameterType="map" resultType="Board">
select * from (
select board.*, row_number() over(order by idx desc) rn from board
)
<!-- 부등호가 들어가는 부분은 CDATA SECTION으로 감싸주자 (문자 데이터 영역을 표시함)-->
<![CDATA[where rn >#{start} and rn<=#{end}]]>
</select>
</mapper>
BoardController
package com.tis.myshop;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.inject.Inject;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import com.tis.common.util.CommonUtil;
import com.tis.domain.BoardVO;
import com.tis.domain.PagingVO;
import com.tis.service.BoardService;
import lombok.extern.log4j.Log4j;
@Controller
@RequestMapping("/board")
@Log4j
public class BoardController {
@Inject
private BoardService boardSvc;
@Inject
private CommonUtil util;
@GetMapping("/write")
public String boardForm() {
return "board/boardWrite";
}
/*
<!-- MultipartResolver빈 등록 [주의사항 id를 반드시 multipartResolver로 등록해야 한다.] -->
<beans:bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<beans:property name="defaultEncoding" value="UTF-8"></beans:property>
<beans:property name="maxUploadSize" value="-1" />
<!-- 업로드 최대 용량은 무제한으로 설정(-1은 무제한) -->
</beans:bean>
파일 업로드를 위해 servlet-context.xml에 위 빈을 등록하자.
*/
@PostMapping("/write")
public String boardWrite(Model m,
HttpServletRequest req,
@ModelAttribute("board") BoardVO board,
@RequestParam("mfilename") MultipartFile mfilename) {
log.info("board==="+board);
log.info("mfilename==="+mfilename);
//업로드할 디렉토리의 절대경로 얻어오기
ServletContext ctx=req.getServletContext();
String upDir=ctx.getRealPath("/Upload");
log.info("upDir==="+upDir);
File dir=new File(upDir);
if(! dir.exists()) {
dir.mkdirs(); //디렉토리 생성
}
//파일을 첨부했다면
if(!mfilename.isEmpty()) {//mfilename이 비어있지 않다면(첨부했다면)
//첨부파일명, 파일크기를 알아내자.
String fname=mfilename.getOriginalFilename(); //원본 파일명
long fsize=mfilename.getSize();//파일크기
//첨부파일이 이미 존재하는 파일일 경우 덮어쓰기를 방지하기 위해
UUID uuid=UUID.randomUUID(); //랜덤한 문자열을 발생시키기 위해 UUID객체를 얻어오자.
String str=uuid.toString();
String fname2=str+"_"+fname; //물리적 파일명
board.setOriginFilename(fname); //원본 파일명 board에 넣는 이유는 DB에서 처리하는게 board에 있기에
board.setFilename(fname2); //물리적 파일명
board.setFilesize(fsize);
//파일 업로드 처리 ==> transferTo()를 이용해서 업로드처리
try {
///////////////////////////업로드 처리해주는 핵심
mfilename.transferTo(new File(upDir,fname2));
///////////////////////////
}catch (IOException e) {
e.printStackTrace();
}
}//if end-------
String mode=board.getMode();
int n=0;
String str = "";
if(mode.equals("write")) {
n=this.boardSvc.insertBoard(board);//글쓰기
str="글쓰기 ";
}else if(mode.equals("rewrite")) {
n=this.boardSvc.rewriteBoard(board);//답변글쓰기
str="답변 글쓰기 ";
}else if(mode.equals("edit")){
n=this.boardSvc.updateBoard(board);//글수정
str="글수정 ";
}
String msg=(n>0)? (str+="성공"):(str+="실패");
String loc=(n>0)?"list":"javascript:history.back()";
return util.addMsgLoc(m, msg, loc);
}
@GetMapping("list")
public String boardListPaging(Model model,HttpServletRequest req,
@ModelAttribute("paging") PagingVO paging) {
log.info("paging=="+paging);
int totalCount=boardSvc.getTotalCount(paging);
paging.setTotalCount(totalCount);
paging.setPagingBlock(5);//페이징 블럭 단위 설정
paging.init(req.getSession());//페이징 연산처리 메소드 호출
log.info("연산 후 paging=="+paging);
List<BoardVO> boardList=boardSvc.selectBoardAllPaging(paging);
String myctx=req.getContextPath();
String loc="board/list";
String naviStr=paging.getPageNavi(myctx, loc);
model.addAttribute("paging",paging);
model.addAttribute("navi",naviStr);
model.addAttribute("boardList",boardList);
return "board/boardList3";
}
@GetMapping("/list_old")
public String boardList(Model model, @RequestParam(defaultValue="1") int cpage,
@RequestParam(defaultValue="10") int pageSize) {
//1.총 게시글 수
int totalCount = this.boardSvc.getTotalCount(null);
//2. 게시 목록
int pageCount=(totalCount-1)/pageSize+1;
if(cpage<1) {
cpage=1;
}
if(cpage>pageCount) {
cpage=pageCount;
}
//DB에서 끊어오기 위한 값 구하기
int end = cpage*pageSize;
int start = end-(pageSize);
Map<String, Integer> map = new HashMap<>();
map.put("start",start);
map.put("end",end);
List<BoardVO> boardList = this.boardSvc.selectBoardAll(map);
model.addAttribute("totalCount",totalCount);
model.addAttribute("pageCount",pageCount);
model.addAttribute("pageSize",pageSize);
model.addAttribute("boardList",boardList);
model.addAttribute("cpage",cpage);
return "board/boardList";
}
@GetMapping("/view")
public String boardView(Model model,@RequestParam(defaultValue="0") int idx) {
if(idx==0) {
return "redirect:list";
}
//조회수 증가
boardSvc.updateReadnum(idx);
//게시글 가져오기
BoardVO board = boardSvc.selectBoardByIdx(idx);
model.addAttribute("board",board);
return "board/boardView";
}
//답변글 달기 form 보여주기
@PostMapping("/rewrite")
public String rewriteForm(Model model,@RequestParam(defaultValue="0") int idx,
@RequestParam(defaultValue="") String subject) {
if(idx==0||subject.isEmpty()) {
return "redirect:list";
}
model.addAttribute("idx",idx);
model.addAttribute("subject",subject);
return "board/boardRewrite";
}
}
pagingVO
package com.tis.domain;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import javax.servlet.http.HttpSession;
import lombok.Data;
@Data
public class PagingVO {
// 페이징 처리관련 프로퍼티
private int cpage;// 현재 보여줄 페이지 번호
private int pageSize;// 한 페이지당 보여줄 목록 개수
private int totalCount;// 총 게시글 수
private int pageCount;// 페이지 수
// DB에서 레코드를 끊어오기 위한 프로퍼티
private int start;
private int end;
// 페이징 블럭 처리 위한 프로퍼티
private int pagingBlock = 5;
private int prevBlock; // 이전 5개
private int nextBlock; // 이후 5개
// 검색 관련 프로퍼티
private String findType;// 검색 유형
private String findKeyword;// 검색어
// 페이징 관련 연산을 수행하는 메소드
public void init(HttpSession ses) {
if (pageSize < 0) {
pageSize = 10;
}
if (pageSize == 0) {
// 파라미터로 pageSize가 넘어오지 않는 경우 세션에서 꺼내보기
Integer ps = (Integer) ses.getAttribute("pageSize");
if (ps == null) {
pageSize = 10;
} else {
pageSize = ps;
}
}
ses.setAttribute("pageSize", pageSize);
pageCount = (totalCount - 1) / pageSize + 1;
if (cpage < 1) {
cpage = 1;
}
if (cpage > pageCount) {
cpage = pageCount;
}
end = cpage * pageSize;
start = end - pageSize;
// 페이징 블럭 연산
prevBlock = (cpage - 1) / pagingBlock * pagingBlock;
nextBlock = prevBlock + (pagingBlock + 1);
}
/** 페이지 네비게이션 문자열을 반환하는 메소드 */
public String getPageNavi(String myctx, String loc) {
// myctx : 컨텍스트명
// loc : 게시판 목록 경로 "/board/list"
// qStr : Query String
findType = (findType == null) ? "" : findType;
try {
findKeyword = (findKeyword == null) ? "" : URLEncoder.encode(findKeyword, "UTF-8");
} catch (UnsupportedEncodingException e) {
}
String qStr = "?pageSize=" + pageSize + "&findType=" + findType + "&findKeyword=" + findKeyword;
// String의 불변성(immutability) 때문에 StringBuffer/StringBuilder
// 를 이용하여 문자열을 편집한 후에 String으로 만들어 반환하자.
StringBuilder buf = new StringBuilder().append("<ul class='pagination'>");
if (prevBlock > 0) {
// 이전 5개
buf.append("<li class='page-item'><a class='page-link' href='" + myctx + "/" + loc + qStr + "&cpage=" + prevBlock + "'>");
buf.append("Prev");
buf.append("</a></li>");
}
for (int i = prevBlock + 1; i <= nextBlock - 1 && i <= pageCount; i++) {
String css = "";
if (i == cpage) {
css = "active";
} else {
css = "";
}
buf.append("<li class='page-item " + css + "'><a class='page-link' href='" + myctx + "/" + loc + qStr + "&cpage=" + i + "'>");
buf.append(i);
buf.append("</a></li>");
} // for--------
if (nextBlock < pageCount) {
// 이후 5개
buf.append("<li class='page-item'><a class='page-link' href='" + myctx + "/" + loc + qStr + "&cpage=" + nextBlock + "'>");
buf.append("Next");
buf.append("</a></li>");
}
buf.append("</ul>");
String str = buf.toString();
// System.out.println(str);
return str;
}
}
boardList.jsp
<%@ 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="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
<c:import url="/top" />
<%
String ctx = request.getContextPath();
%>
<script type="text/javascript">
var check = function() {
if (!sf.findType.value) {
alert('검색 유형을 선택하세요');
sf.findType.focus();
return false;
}
if (!sf.findKeyword.value) {
alert('검색어를 입력하세요');
sf.findKeyword.focus();
return false;
}
sf.submit();
}
</script>
<div class="row">
<div align="center" id="bbs" class="col-md-10 offset-md-1">
<h1>Spring Board</h1>
<p>
<a href="<%=ctx%>/board/write#bbs">글쓰기</a>| <a
href="<%=ctx%>/board/list#bbs">글목록</a>
<p>
<!-- 검색 폼 시작--------------------- -->
<form name="sf" action="find" onsubmit="return check()">
<div class="row m-4">
<div class="col-md-2">
<select name="findType" class="form-control">
<option value="">::검색 유형::</option>
<option value="1">글제목</option>
<option value="2">작성자</option>
<option value="3">글내용</option>
</select>
</div>
<div class="col-md-7">
<input type="text" name="findKeyword" class="form-control"
placeholder="검색어를 입력하세요">
</div>
<div class="col-md-2">
<button type="button" onclick="check()" class="btn btn-warning">검색</button>
</div>
</div>
<!-- row end -->
</form>
<!-- 검색 폼 끝---------------------- -->
<table class="table table-condensed table-striped">
<tr>
<th>글번호</th>
<th>제목</th>
<th>글쓴이</th>
<th>날짜</th>
<th>조회수</th>
</tr>
<!-- ---------------------------- -->
<c:forEach var="board" items="${boardList}">
<tr>
<td>${board.idx}</td>
<td align="left">
<c:forEach var="k" begin="0" end="${board.lev}">
</c:forEach>
<c:if test="${board.lev>0}">
<img src="../images/re.PNG">
</c:if>
<a href="view?idx=${board.idx}">
${board.subject} </a></td>
<td>${board.name}</td>
<td><fmt:formatDate value="${board.wdate}"
pattern="yyyy-MM-dd" /></td>
<td>${board.readnum}</td>
</tr>
</c:forEach>
<!-- ----------------------------- -->
<tr>
<td colspan="3" class="text-center">
${navi}
</td>
<td colspan="2">총게시물수: <span class="text-danger"
style="font-weight: bold">${paging.totalCount}개</span> <br> <span
class="text-danger" style="font-weight: bold">${paging.cpage}</span>
/${paging.pageCount} pages
</td>
</tr>
</table>
</div>
</div>
<c:import url="/foot" />
boardRewrite.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<c:import url="/top" />
<script type="text/javascript">
$(function() {
$('#btnWrite').click(function() {
if (!$('#subject').val()) {
alert('글제목을 입력하세요');
$('#subject').focus();
return;
}
if (!$('#name').val()) {
alert('글쓴이를 입력하세요');
$('#name').focus();
return;
}
if (!$('#bpwd').val()) {
alert('비밀번호를 입력하세요');
$('#bpwd').focus();
return;
}
$('#bf').submit();
});
});
</script>
<%
String ctx = request.getContextPath();
%>
<div class="section">
<div class="row">
<div align="center" id="bbs" class="col-md-10 offset-1">
<h1>Spring Board 답변 쓰기</h1>
<p>
<a href="<%=ctx%>/board/write#bbs">글쓰기</a>| <a
href="<%=ctx%>/board/list#bbs">글목록</a>
<p>
<!--파일 업로드시
method: POST
enctype: multipart/form-data
[1] 글쓰기 => input [mode=write]
[2] 글수정 => input [mode=Edit]
[3] 답글쓰기=> input [mode=rewrite]
-->
<form name="bf" id="bf" role="form" action="write"
method="POST" enctype="multipart/form-data">
<!-- 부모글의 글번호(idx)와 mode값(rewrite)를 hidden데이터로 넘기자 -->
<input type="hidden" name="idx" value="<c:out value="${idx}"/>">
<input type="hidden" name="mode" value="rewrite">
<!-- ------------------------------------------ -->
<table class="table table-bordered">
<tr>
<td style="width:20%"><b>제목</b></td>
<td style="width:80%">
<input type="text" name="subject"
value="[RE]<c:out value="${subject}"/>"
id="subject" class="form-control">
</td>
</tr>
<tr>
<td style="width:20%"><b>글쓴이</b></td>
<td style="width:80%">
<input type="text" name="name" id="name" class="form-control">
</td>
</tr>
<tr>
<td style="width:20%"><b>글내용</b></td>
<td style="width:80%">
<textarea name="content" id="content" rows="10" cols="50"
class="form-control"></textarea>
</td>
</tr>
<tr>
<td style="width:20%"><b>비밀번호</b></td>
<td style="width:80%">
<div class="col-md-5">
<input type="password" name="pwd" id="bpwd" class="form-control">
</div>
</td>
</tr>
<tr>
<td style="width: 20%"><b>첨부파일</b></td>
<td style="width: 80%">
<input type="file" name="mfilename"
id="filename" class="form-control"></td>
</tr>
<tr>
<td colspan="2">
<button type="button" id="btnWrite" class="btn btn-success">글쓰기</button>
<button type="reset" id="btnReset" class="btn btn-warning">다시쓰기</button>
</td>
</tr>
</table>
</form>
</div>
</div>
</div>
<c:import url="/foot"/>
BoardServiceImpl.java
@Override
public int rewriteBoard(BoardVO board) {
//1. 부모(원글)글의 refer, lev, sunbun 값 가져오기 (select)
BoardVO parent = boardMapper.selectRefLevSunbun(board.getIdx());
//2. 기존에 달린 답변글들이 있다면 sunbun을 하나씩 뒤로 밀어내기 하자
// 왜? 내가 쓴 답변글이 끼어들어야 하므로(update문)
boardMapper.updateSunbun(parent);
//3. 내가 쓴 답변글을 insert 한다 (insert문)
// 이 때 부모글 그룹번호 (refer)와 동일하게, lev은 부모의 lev+1, sunbun도 부모의 sunbun+1
board.setRefer(parent.getRefer());//부모와 같은 글그룹 번호 지정
board.setLev(parent.getLev()+1);
board.setSunbun(parent.getSunbun()+1);
return this.boardMapper.rewriteBoard(board);
}
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.tis.mapper.BoardMapper">
<!-- refer컬럼은 글그룹번호 원글의 글번호를 인덱스로 하자-->
<insert id="insertBoard" parameterType="Board">
<selectKey keyProperty="idx" keyColumn="idx" order="BEFORE" resultType="int">
select board_seq.nextval from dual
</selectKey>
insert into board values(#{idx},#{name},#{pwd},#{subject},
#{content:VARCHAR},sysdate,0,#{filename:VARCHAR},#{originFilename:VARCHAR},
#{filesize:NUMERIC},#{idx},#{lev},#{sunbun})
</insert>
<select id="getTotalCount" resultType="int">
select count(*) from board
</select>
<select id="selectBoardAllPaging" parameterType="Paging" resultType="Board">
select * from (
select board.*, row_number() over(order by refer desc, sunbun asc) rn from board
)
<!-- 부등호가 들어가는 부분은 CDATA SECTION으로 감싸주자 (문자 데이터 영역을 표시함)-->
<![CDATA[where rn >#{start} and rn<=#{end}]]>
</select>
<update id="updateReadnum" parameterType="int">
update board set readnum = readnum+1 where idx=#{idx}
</update>
<select id="selectBoardByIdx" parameterType="int" resultType="Board">
select * from board where idx=#{idx}
</select>
<!-- [답변글쓰기 관련] 부모글의 refer,lev,sunbun가져오기 -->
<select id="selectRefLevSunbun" parameterType="int" resultType="Board">
select refer, lev, sunbun from board where idx=#{idx}
</select>
<!-- [답변글스기 관련] 2단계 기존에 달려있는 답변글들이 있다면 sunbun을 하나씩 뒤로 밀어내기 한다.-->
<update id="updateSunbun" parameterType="Board">
update board set sunbun = sunbun+1 where refer=#{refer} and sunbun > #{sunbun}
</update>
<!-- [답변글스기 관련] 3단계 답변글 등록하기-->
<insert id="rewriteBoard" parameterType="Board">
<selectKey keyProperty="idx" keyColumn="idx" order="BEFORE" resultType="int">
select board_seq.nextval from dual
</selectKey>
insert into board values(#{idx},#{name},#{pwd},#{subject},
#{content:VARCHAR},sysdate,0,#{filename:VARCHAR},#{originFilename:VARCHAR},
#{filesize:NUMERIC},#{refer},#{lev},#{sunbun})
</insert>
</mapper>
'개발자 > 국비지원 SW' 카테고리의 다른 글
국비지원 109일차 - AndroidStudio 시작, 메모장 예제(화면 이동) (0) | 2020.09.28 |
---|---|
국비지원 108일차 - VueJS db연동 (0) | 2020.09.28 |
국비지원 106일차 - Vue 컴포넌트 Lifecycle, vue-ajax, axios VueCli생성 (0) | 2020.09.23 |
국비지원 105일차 - Spring 결제 확인까지 구현 (0) | 2020.09.23 |
국비지원 104일차 - Vue 인스턴스, 컴포넌트(전역, 지역, 통신), 이벤트 버스, To Do App (0) | 2020.09.21 |