SPRING - SPRING FRAMEWORK 3 db fileupload
이 포스트에 설정된 상태로 진행
+) mybatis-config.xml에 dto등록 및 boardMapper.xml 생성
답변형 게시판 db
[답변형게시판]
num : auto
-> num,regroup,restep,relevel 값은 무조건 넘어가야한다(이 4가지로 답글작성글쓰기 구성가능)
-> num이 넘어오면 답글로 간주 num이 안넘어오면 새글로 간주
writer
pass
subject
content
photo
readcount
(답변형 댓글 3가지 더 필요)
①regroup : 새글+예전글
②restep : 출력
③relevel :들여쓰기
writeday
regroup은 a글에 해당하는 답글들을 묶어줄 group용 column
ex) db에 자료가 아예 없을때 maxNum값이 regroup에 들어간다 이때 num값은 없으니 0 regroup은 1 restep은 0 relevel은 0
이를 a라 칭하고
a의 답글들은 전부 regroup이 1
이후 b라는 새 글이 작성되면 a 원글+a답글들의 최대 num값이 regroup에 들어간다
-> a 원글+a답글들의 최대 num 값이 6이면 b글의 regroup은 7
restep은 a글에 해당하는 답글들의 순서를 나열하기 위한 것
a글의 답글을 적으면 a에 붙어있어야한다
num 1 원글 a면 regroup 1 restep 0 relevel 0
num 2 a의 답글1 regroup 1 restep 1 relevel 1
num 3 a의 답글2 regroup 1 restep 1 relevel 1 -> num 2의 restep은 2로 변경됨
num 4 num2(a의 답글1)의 답글1 regroup 1 restep 3 relevel 2 -> num2를 기준으로 restep 2+1 relevel은 답글의 답글이니까 1칸 들여쓰기해서 2가 되어야함
num 5 a의 답글3 regroup 1 restep 1 relvel 1 -> num1을 기준으로 num5의 regroup이 +1되고 num 234의 regroup +1씩 해서 번호 밀려남
num 6 num2(a의 답글1)의 답글2 regroup 1 restep 4 relevel 1
-> num2를 기준 현재 num 2의 regroup은 3이니까 num 6의 regroup은 +1 해서 4
-> num2를 기준으로 num 2의 relevel은 1이니까 num 6의 relevel은 +1 해서 2
jsp에서는 parameter가 무조건 String으로 넘어가서 String으로 해줬지만, Controller에서 int로 바꿔주면 된다
package spring.mvc.reboard;
import java.sql.Timestamp;
public class BoardDto {
//jsp에서는 parameter가 무조건 String으로 넘어가서 String으로 해줬지만, Controller에서 int로 바꿔주면 된다
private int num;
private String writer;
private String pass;
private String subject;
private String content;
private String photo;
private int regroup;
private int restep;
private int relevel;
private int readcount;
private Timestamp writeday;
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public String getWriter() {
return writer;
}
public void setWriter(String writer) {
this.writer = writer;
}
public String getPass() {
return pass;
}
public void setPass(String pass) {
this.pass = pass;
}
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getPhoto() {
return photo;
}
public void setPhoto(String photo) {
this.photo = photo;
}
public int getRegroup() {
return regroup;
}
public void setRegroup(int regroup) {
this.regroup = regroup;
}
public int getRestep() {
return restep;
}
public void setRestep(int restep) {
this.restep = restep;
}
public int getRelevel() {
return relevel;
}
public void setRelevel(int relevel) {
this.relevel = relevel;
}
public int getReadcount() {
return readcount;
}
public void setReadcount(int readcount) {
this.readcount = readcount;
}
public Timestamp getWriteday() {
return writeday;
}
public void setWriteday(Timestamp writeday) {
this.writeday = writeday;
}
}
paging처리 전체 리스트(전체 조회+페이징)
처리 해줄 변수가 2가지(startNum,perPage)로 파라메터 값에 HashMap
regroup은 내림차순(최신이 제일 위) restep은 오름차순
<!-- paging처리 전체 리스트(전체 조회+페이징)
처리 해줄 변수가 2가지(startNum,perPage)로 파라메터 값에 HashMap
regroup은 내림차순(최신이 제일 위) restep은 오름차순 -->
<select id="SelectPagingOfReboard" resultType="bdto" parameterType="HashMap">
select * from reboard order by regroup desc,restep asc limit #{start},#{perpage}
</select>
public List<BoardDto> getPagingList(int start,int perpage);
HashMap<String, Integer> map=new HashMap<String, Integer>();
@Override
public List<BoardDto> getPagingList(int start, int perpage) {
// TODO Auto-generated method stub
// map.put(String값,Integer값)
HashMap<String, Integer> map=new HashMap<String, Integer>();
map.put("start", start);
map.put("perpage", perpage);
return session.selectList("SelectPagingOfReboard", map);
}
@RequestParam(value = "currentPage",defaultValue = "1") int currentPage)
List<BoardDto> list=dao.getPagingList(startNum, perPage);
model.addObject("totalCount", totalCount);
model.addObject("list", list);
model.addObject("startPage", startPage);
model.addObject("endPage", endPage);
model.addObject("totalPage", totalPage);
model.addObject("no", no);
model.addObject("currentPage", currentPage);
@GetMapping("/board/list")
public ModelAndView list(
@RequestParam(value = "currentPage",defaultValue = "1") int currentPage)
{
ModelAndView model=new ModelAndView();
//페이징 처리에 필요한 변수 선언
int totalCount=dao.getTotalCount();
int totalPage; //총 페이지수
int startPage; //각블럭에서 보여질 시작페이지
int endPage; //각블럭에서 보여질 끝페이지
int startNum; //db에서 가져올 글의 시작번호(mysql은 첫글이 0,오라클은 1)
int perPage=10; //한페이지당 보여질 글의 갯수
int perBlock=5; //한블럭당 보여질 페이지 개수
//총페이지수 구하기
//총글의 갯수/한페이지당 보여질 개수로 나눔(7/5=1)
//나머지가 1이라도 있으면 무조건 1페이지 추가(1+1=2페이지가 필요)
totalPage=totalCount/perPage+(totalCount%perPage==0?0:1);
//각블럭당 보여야할 시작페이지
//perBlock=5일경우는 현재페이지 1~5 시작:1 끝:5
//현재페이지 13 시작:11 끝:15
startPage=(currentPage-1)/perBlock*perBlock+1;
endPage=startPage+perBlock-1;
// 총페이지가 23일경우 마지막블럭은 25가아니라 23이다
if(endPage>totalPage)
endPage=totalPage;
//각페이지에서 보여질 시작번호
//1페이지: 0,2페이지:5 3페이지:10....
startNum=(currentPage-1)*perPage;
//각 페이지에서 필요한 게시글 가져오기
List<BoardDto> list=dao.getPagingList(startNum, perPage);
//각 페이지에 출력할 시작번호
int no=totalCount-(currentPage-1)*perPage;
model.addObject("totalCount", totalCount);
model.addObject("list", list);
model.addObject("startPage", startPage);
model.addObject("endPage", endPage);
model.addObject("totalPage", totalPage);
model.addObject("no", no);
model.addObject("currentPage", currentPage);
model.setViewName("reboard/boardlist");
return model;
}
출력 후 감소
<td align="center">${no }</td>
<c:set var="no" value="${no-1 }"/>
원글은 relevel 기본값이 0이라서 forEach 안돌음
<c:forEach var="s" begin="1" end="${dto.relevel }">
<!-- 원글은 relevel이 0이라서 안돌음 -->
</c:forEach>
<c:if test="${dto.relevel>0 }">
<img alt="" src="../upload/re.png">
</c:if>
<a href="content?num=${dto.num }¤tPage=${currentPage}">${dto.subject }</a>
<c:if test="${dto.photo!='no' }">
<i class="bi bi-image-fill"></i>
</c:if>
<td align="center">${dto.writer }</td>
<td>
<fmt:formatDate value="${dto.writeday }" pattern="yyyy-MM-dd"/>
</td>
<td align="center">
${dto.readcount }
</td>
<!-- 페이징 -->
<c:if test="${totalCount>0 }">
<div style="width: 800px; text-align: center;">
<ul class="pagination justify-content-center">
<!-- 이전 -->
<c:if test="${startPage>1 }">
<li><a href="list?currentPage=${startPage-1 }">이전</a></li>
</c:if>
<c:forEach var="pp" begin="${startPage }" end="${endPage }">
<c:if test="${currentPage==pp }">
<li class="page-item active">
<a class="page-link" href="list?currentPage=${pp }">${pp }</a>
</li>
</c:if>
<c:if test="${currentPage!=pp }">
<li class="page-item">
<a class="page-link" href="list?currentPage=${pp }">${pp }</a>
</li>
</c:if>
</c:forEach>
<!-- 다음 -->
<c:if test="${endPage<totalPage }">
<li class="page-item">
<a class="page-link" href="list?currentPage=${endPage+1 }">다음</a>
</li>
</c:if>
</ul>
</div>
</c:if>
<%@ 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" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Cute+Font&family=Diphylleia&family=Dokdo&family=Nanum+Brush+Script&family=Nanum+Gothic+Coding&family=Noto+Sans+KR&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
<script src="https://code.jquery.com/jquery-3.7.0.js"></script>
<title>Insert title here</title>
</head>
<body>
<div style="margin: 50px 50px;">
<table class="table table-bordered" style="width: 800px;">
<caption align="top"><b>스프링 답변형 게시판</b>
<span style="float: right;">
<button type="button" class="btn btn-outline-info"
onclick="location.href='writeform'">글쓰기</button>
</span>
</caption>
<tr>
<th width="60">번호</th>
<th width="300">제목</th>
<th width="300">작성자</th>
<th width="300">작성일</th>
<th width="300">조회</th>
</tr>
<c:if test="${totalCount==0 }">
<tr>
<td colspan="5" align="center">
<b>등록된 게시글이 없습니다</b>
</td>
</tr>
</c:if>
<c:if test="${totalCount>0 }">
<c:forEach var="dto" items="${list }">
<tr>
<td align="center">${no }</td>
<c:set var="no" value="${no-1 }"/> <!-- 출력 후 감소 -->
<td> <!-- 제목 -->
<!-- relevel 만큼 공백...보통 2칸씩 -->
<c:forEach var="s" begin="1" end="${dto.relevel }">
<!-- 원글은 relevel이 0이라서 안돌음 -->
</c:forEach>
<!-- 답글인 경우에만 re.png이미지 출력 -->
<c:if test="${dto.relevel>0 }">
<img alt="" src="../upload/re.png">
</c:if>
<!-- 제목...여기 누르면 내용 보기로... -->
<a href="content?num=${dto.num }¤tPage=${currentPage}">${dto.subject }</a>
<!-- 사진이 있을 경우 아이콘 표시 -->
<c:if test="${dto.photo!='no' }">
<i class="bi bi-image-fill"></i>
</c:if>
</td>
<td align="center">${dto.writer }</td>
<td>
<fmt:formatDate value="${dto.writeday }" pattern="yyyy-MM-dd"/>
</td>
<td align="center">
${dto.readcount }
</td>
</tr>
</c:forEach>
</c:if>
</table>
<!-- 페이징 -->
<c:if test="${totalCount>0 }">
<div style="width: 800px; text-align: center;">
<ul class="pagination justify-content-center">
<!-- 이전 -->
<c:if test="${startPage>1 }">
<li><a href="list?currentPage=${startPage-1 }">이전</a></li>
</c:if>
<c:forEach var="pp" begin="${startPage }" end="${endPage }">
<c:if test="${currentPage==pp }">
<li class="page-item active">
<a class="page-link" href="list?currentPage=${pp }">${pp }</a>
</li>
</c:if>
<c:if test="${currentPage!=pp }">
<li class="page-item">
<a class="page-link" href="list?currentPage=${pp }">${pp }</a>
</li>
</c:if>
</c:forEach>
<!-- 다음 -->
<c:if test="${endPage<totalPage }">
<li class="page-item">
<a class="page-link" href="list?currentPage=${endPage+1 }">다음</a>
</li>
</c:if>
</ul>
</div>
</c:if>
</div>
</body>
</html>
@GetMapping("/board/writeform")
public ModelAndView writeform(@RequestParam Map<String, String> map)
{
ModelAndView model=new ModelAndView();
//밑 5개는 답글일 경우에만 넘어온다 (새 글일 경우 안넘어옴)
String currentPage=map.get("currentPage");
String num=map.get("num");
String regroup=map.get("regroup");
String restep=map.get("restep");
String relevel=map.get("relevel");
//새글이라면 Null 답글이라면 숫자
System.out.println(currentPage+","+num);
//입력폼에 hidden으로 넣어줘야함...답글일때 대비
model.addObject("currentPage", currentPage==null?"1":"currentPage"); //1페이지일경우:n번째페이지일 경우
model.addObject("num", num==null?"0":num); //0이어야 dao에서 새글로 인식 가능
model.addObject("regroup", regroup==null?"0":regroup);
model.addObject("restep", restep==null?"0":restep);
model.addObject("relevel", relevel==null?"0":relevel);
//0으로 넣어야 dao에서 새 글로 인식
//폼이 답글,새글 공용이므로
model.setViewName("reboard/writeform");
return model;
}
//밑 5개는 답글일 경우에만 넘어온다 (새 글일 경우 안넘어옴)
String currentPage=map.get("currentPage");
String num=map.get("num");
String regroup=map.get("regroup");
String restep=map.get("restep");
String relevel=map.get("relevel");
새글이라면 Null 답글이라면 숫자
System.out.println(currentPage+","+num);
입력폼에 hidden으로 넣어줘야함...답글일때 대비
폼이 답글,새글 공용이므로
0으로 넣어야 dao에서 새 글로 인식
//입력폼에 hidden으로 넣어줘야함...답글일때 대비
model.addObject("currentPage", currentPage==null?"1":"currentPage"); //1페이지일경우:n번째페이지일 경우
model.addObject("num", num==null?"0":num); //0이어야 dao에서 새글로 인식 가능
model.addObject("regroup", regroup==null?"0":regroup);
model.addObject("restep", restep==null?"0":restep);
model.addObject("relevel", relevel==null?"0":relevel);
//0으로 넣어야 dao에서 새 글로 인식
//폼이 답글,새글 공용이므로
input hidden으로 num,regroup,restep,relevel 값을 넘겨줘야 새글/답글 작성 가능
<input type="hidden" name="num" value="${num }">
<input type="hidden" name="regroup" value="${regroup }">
<input type="hidden" name="restep" value="${restep }">
<input type="hidden" name="relevel" value="${relevel }">
<input type="hidden" name="currentPage" value="${currentPage }">
<%@ 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" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Cute+Font&family=Diphylleia&family=Dokdo&family=Nanum+Brush+Script&family=Nanum+Gothic+Coding&family=Noto+Sans+KR&display=swap" rel="stylesheet">
<script src="https://code.jquery.com/jquery-3.7.0.js"></script>
<title>Insert title here</title>
</head>
<body>
<div style="margin: 50px 100px;">
<form action="insert" method="post" enctype="multipart/form-data">
<!-- hidden 5개 paging이 없다면 4개 -->
<input type="hidden" name="num" value="${num }">
<input type="hidden" name="regroup" value="${regroup }">
<input type="hidden" name="restep" value="${restep }">
<input type="hidden" name="relevel" value="${relevel }">
<input type="hidden" name="currentPage" value="${currentPage }">
<table class="table table-bordered" style="width: 500px;">
<caption align="top"><b>
<c:if test="${num==0 }">새글쓰기</c:if>
<c:if test="${num!=0 }">답글쓰기</c:if>
</b></caption>
<tr>
<th>작성자</th>
<td>
<input type="text" name="writer" class="form-control" required="required"
style="width: 120px;">
</td>
</tr>
<tr>
<th>비밀번호</th>
<td>
<input type="password" name="pass" class="form-control" required="required"
style="width: 130px;">
</td>
</tr>
<tr>
<th>제목</th>
<td>
<input type="text" name="subject" class="form-control" required="required"
style="width: 300px;">
</td>
</tr>
<tr>
<th>사진</th>
<td>
<input type="file" name="uimage" class="form-control" style="width: 220px;" multiple="multiple">
</td>
</tr>
<tr>
<td colspan="2">
<textarea style="width: 480px; height: 130px;" name="content" required="required"
class="form-control"></textarea>
</td>
</tr>
<tr>
<td colspan="2" align="center">
<button type="submit" class="btn btn-outline-info" style="width: 100px;">저장하기</button>
<button type="button" class="btn btn-outline-success" style="width: 100px;"
onclick="location.href='list'">목록</button>
</td>
</tr>
</table>
</form>
</div>
</body>
</html>
<!-- insert: 원글, 답글 모두 해당 -->
<insert id="insertOfReboard" parameterType="bdto">
insert into reboard (writer,pass,subject,content,photo,regroup,restep,relevel,writeday)
values(#{writer},#{pass},#{subject},#{content},#{photo},#{regroup},#{restep},#{relevel},now())
</insert>
<!-- num의 max값,null일 경우 0 -->
<select id="MaxNumOfReboard" resultType="int">
select ifnull(max(num),0) from reboard
</select>
<!-- 같은 group중에서 넘어오는 step보다 큰 step 데이터는 모두 +1
2가지(regroup,restep)가 넘어와야하기 때문에 HashMap 반환값 -->
<update id="UpdateStepOfReBoard" parameterType="HashMap">
update reboard set restep=restep+1 where regroup=#{regroup} and restep>#{restep}
</update>
public void insertReboard(BoardDto dto);
int num=dto.getNum();
int regroup=dto.getRegroup();
int restep=dto.getRestep();
int relevel=dto.getRelevel();
if(num==0)
{
//새 글일 경우 새로운 그룹을 만들어줘서 num값+1해줌(답글 달 경우 모두 regroup으로 묶어주려고)
regroup=getMaxNum()+1;
restep=0;
relevel=0;
else {
//넘어오는 값(num)이 있으면 답글
//같은 그룹 중 전달받은 restep보다 큰 글들 모두 +1
//만든 메서드 불러오기...this.은 생략 가능
this.updateRestep(regroup, restep);
//전달 받은 step과 level 모두 1 증가
restep++;
relevel++;
}
@Override
public void insertReboard(BoardDto dto) {
// TODO Auto-generated method stub
//insert 시 필요한 4가지 값 가져오기
int num=dto.getNum();
int regroup=dto.getRegroup();
int restep=dto.getRestep();
int relevel=dto.getRelevel();
//넘어오는 값이 없으면 새글
if(num==0)
{
//새 글일 경우 새로운 그룹을 만들어줘서 num값+1해줌(답글 달 경우 모두 regroup으로 묶어주려고)
regroup=getMaxNum()+1;
restep=0;
relevel=0;
}else {
//넘어오는 값이 있으면 답글
//같은 그룹 중 전달받은 restep보다 큰 글들 모두 +1
//만든 메서드 불러오기...this.은 생략 가능
this.updateRestep(regroup, restep);
//전달 받은 step과 level 모두 1 증가
restep++;
relevel++;
}
//바뀐 값들을 다시 dto에 담는다
dto.setRegroup(regroup);
dto.setRestep(restep);
dto.setRelevel(relevel);
//insert
session.insert("insertOfReboard", dto);
}
0번지 사진을 항상 우선 하기 때문에 get(0)
uimage는 배열이기 때문에 0번지가 빈 문자열이면 사진이 없을 경우라는 뜻
if(uimage.get(0).getOriginalFilename().equals(""))
photo="no";
배열 0번지가 비어있으면(사진이 없으면) no문자 db에 입력
else {
for(MultipartFile f:uimage)
{
String fName=sdf.format(new Date())+"_"+f.getOriginalFilename();
photo+=fName+",";
try {
f.transferTo(new File(path+"/"+fName));
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//for문 안 photo에서 마지막 , 제거(길이 제일 마지막에 , 있음)
photo=photo.substring(0, photo.length()-1);
}
photo=photo.substring(0, photo.length()-1);
dto의 photo에 넣기
dto.setPhoto(photo);
dao.insertReboard(dto);
int num=dao.getMaxNum();
return "redirect:content?num="+num+"¤tPage="+currentPage;
@PostMapping("/board/insert")
public String insert(@ModelAttribute BoardDto dto,
@RequestParam ArrayList<MultipartFile> uimage,
HttpSession session,
@RequestParam String currentPage)
{
//실제경로
String path=session.getServletContext().getRealPath("/WEB-INF/photo");
System.out.println(path);
SimpleDateFormat sdf=new SimpleDateFormat("yyyyMMddHHmmss");
String photo="";
//0번지 사진을 항상 우선 하기 때문에 get(0)...uimage는 배열이기 때문에 0번지가 빈 문자열이면 사진이 없을 경우라는 뜻
if(uimage.get(0).getOriginalFilename().equals(""))
photo="no";
else {
for(MultipartFile f:uimage)
{
String fName=sdf.format(new Date())+"_"+f.getOriginalFilename();
photo+=fName+",";
try {
f.transferTo(new File(path+"/"+fName));
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//for문 안 photo에서 마지막 , 제거(길이 제일 마지막에 , 있음)
photo=photo.substring(0, photo.length()-1);
}
//dto의 photo에 넣기
dto.setPhoto(photo);
//insert
dao.insertReboard(dto);
//insert는 num값을 넣을때 항상 maxNum으로 db에 들어가기 때문에 MaxNum으로 값 값 넘겨줘서 content로 넘어가게 해줘야한다
int num=dao.getMaxNum();
return "redirect:content?num="+num+"¤tPage="+currentPage; //content 일단 없으니까 목록으로
}