게시판(페이징,블록,답글)

/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}">
						&nbsp;&nbsp;&nbsp;
					</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>

+ Recent posts