ㅁ board-mapper.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="boardMapper">
<resultMap id="boardResultMap" type="BoardDto">
<result column="board_no" property="boardNo" />
<result column="board_title" property="boardTitle" />
<result column="user_id" property="boardWriter" />
<result column="count" property="count" />
<result column="regist_date" property="registDt" />
<result column="attach_count" property="attachCount" />
</resultMap>
<select id="selectBoardListCount" resultType="_int">
select
count(*)
from board
where status = 'Y'
</select>
<select id="selectBoardList" resultMap="boardResultMap">
select
b.board_no
, board_title
, user_id
, count
, to_char(regist_date, 'YYYY-MM-DD') as "regist_date"
, (
select count(*)
from attachment
where ref_type = 'B'
and ref_no = b.board_no
) as "attach_count"
from board b
join member on (user_no = board_writer)
where b.status = 'Y'
order by board_no desc
</select>
<select id="selectSearchListCount" resultType="_int">
select
count(*)
from board b
join member on (user_no = board_writer)
where b.status = 'Y'
and ${condition} like '%' || #{keyword} || '%'
</select>
<select id="selectSearchList" resultMap="boardResultMap">
select
b.board_no
, board_title
, user_id
, count
, to_char(regist_date, 'YYYY-MM-DD') as "regist_date"
, (
select count(*)
from attachment
where ref_type = 'B'
and ref_no = b.board_no
) as "attach_count"
from board b
join member on (user_no = board_writer)
where b.status = 'Y'
and ${condition} like '%' || #{keyword} || '%'
order by board_no desc
</select>
<insert id="insertBoard">
insert
into board
(
board_no
, board_title
, board_writer
, board_content
)
values
(
seq_bno.nextval
, #{boardTitle}
, #{boardWriter}
, #{boardContent}
)
</insert>
<insert id="insertAttach">
insert
into attachment
(
file_no
, file_path
, filesystem_name
, original_name
, ref_type
, ref_no
)
values
(
seq_ano.nextval
, #{filePath}
, #{filesystemName}
, #{originalName}
, #{refType}
, seq_bno.currval
)
</insert>
</mapper>
ㅁ BoardDto 수정
package com.br.spring.dto;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@ToString
@Builder
public class BoardDto {
private int boardNo;
private String boardTitle;
private String boardWriter;
private String boardContent;
private int count;
private String registDt;
private String status;
private int attachCount;
private List<AttachDto> attachList; // has many 관계
// private AttachDto attach; 만약 게시글에 첨부파일이 하나였다면 has a 관계
}
- attachList 필드 추가.
ㅁ fileValidate.js
$(document).ready(function(){
$('.file').on('change', function(evt){
const files = evt.target.files; // FileList {0: File, 1: File, ...}
let totalSize = 0;
for(let i=0; i<files.length; i++){
if(files[i].size > 10*1024*1024){
alert('첨부파일의 최대 크기는 10MB입니다');
evt.target.value = '';
return;
}
totalSize += files[i].size;
if(totalSize > 100*1024*1024){
alert('전체 첨부파일의 최대 크기는 100MB입니다');
evt.target.value = '';
return;
}
}
})
})
- 이렇게 작성했었다.
ㅁ FileUtil 수정
package com.br.spring.util;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
@Component
public class FileUtil {
public Map<String, String> fileupload(MultipartFile uploadFile, String folderName) {
// (1) 업로드할 폴더 (/upload/yyyyMMdd)
String filePath = "/upload/" + folderName + new SimpleDateFormat("/yyyyMMdd").format(new Date());
File filePathDir = new File(filePath);
if(!filePathDir.exists()) { // 해당 경로의 폴더가 존재하지 않을 경우
filePathDir.mkdirs(); // 해당 폴더 만들기
}
// (2) 파일명 수정 작업
String originalFilename = uploadFile.getOriginalFilename(); // "xxxxx.jpg" | "xxxx.tar.gz" (파일 확장자가 2단계로 된 확장자도 있다)
// 원본명으로부터 확장자 추출하기
String originalExt = originalFilename.endsWith(".tar.gz") ? ".tar.gz"
: originalFilename.substring(originalFilename.lastIndexOf("."));
String filesystemName = UUID.randomUUID().toString().replace("-", "") + originalExt;
// (3) 업로드 (폴더에 파일 저장)
try {
uploadFile.transferTo(new File(filePathDir, filesystemName));
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
// db에 기록할 데이터 다시 반환
Map<String, String> map = new HashMap<>();
map.put("filePath", filePath);
map.put("originalName", originalFilename);
map.put("filesystemName", filesystemName);
return map;
}
}
- map.put("originalFileName", originalFilename);을
map.put("originalName", originalFilename);로 수정했다.
ㅁ list.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:set var="contextPath" value="${pageContext.request.contextPath}" />
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<style>
#boardList th, #boardList td:not(:nth-child(2)){text-align: center;}
#boardList>tbody>tr:hover{cursor:pointer;}
</style>
</head>
<body>
<div class="container p-3">
<!-- Header, Nav start -->
<jsp:include page="/WEB-INF/views/common/header.jsp"/>
<!-- Header, Nav end -->
<!-- Section start -->
<section class="row m-3" style="min-height: 500px">
<div class="container border p-5 m-4 rounded">
<h2 class="m-4">게시글 목록</h2>
<br>
<c:if test="${ not empty loginUser }">
<!-- 로그인후 상태일 경우만 보여지는 글쓰기 버튼-->
<a class="btn btn-secondary" style="float:right" href="${contextPath}/board/regist.do">글쓰기</a>
<br>
</c:if>
<br>
<table id="boardList" class="table table-hover" align="center">
<thead>
<tr>
<th width="100px">번호</th>
<th width="400px">제목</th>
<th width="120px">작성자</th>
<th>조회수</th>
<th>작성일</th>
<th>첨부파일</th>
</tr>
</thead>
<tbody>
<c:choose>
<c:when test="${ empty list }">
<tr>
<td colspan="6">조회된 게시글이 없습니다.</td>
</tr>
</c:when>
<c:otherwise>
<c:forEach var="b" items="${ list }">
<tr>
<td>${ b.boardNo }</td>
<td>${ b.boardTitle }</td>
<td>${ b.boardWriter }</td>
<td>${ b.count }</td>
<td>${ b.registDt }</td>
<td>${ b.attachCount > 0 ? '*' : '' }</td>
</tr>
</c:forEach>
</c:otherwise>
</c:choose>
</tbody>
</table>
<br>
<ul id="paging_area" class="pagination d-flex justify-content-center">
<li class="page-item ${ pi.currentPage == 1 ? 'disabled' : '' }">
<a class="page-link" href="${ contextPath }/board/list.do?page=${pi.currentPage-1}">Previous</a>
</li>
<c:forEach var="p" begin="${ pi.startPage }" end="${ pi.endPage }">
<li class="page-item ${ pi.currentPage == p ? 'active' : '' }">
<a class="page-link" href="${contextPath}/board/list.do?page=${ p }">${ p }</a>
</li>
</c:forEach>
<li class="page-item ${ pi.currentPage == pi.maxPage ? 'disabled' : '' }">
<a class="page-link" href="${ contextPath }/board/list.do?page=${pi.currentPage+1}">Next</a>
</li>
</ul>
<br clear="both"><br>
<form id="search_form" action="${contextPath }/board/search.do" method="get" class="d-flex justify-content-center">
<input type="hidden" name="page" value="1">
<div class="select" >
<select class="custom-select" name="condition">
<option value="user_id">작성자</option>
<option value="board_title">제목</option>
<option value="board_content">내용</option>
</select>
</div>
<div class="text">
<input type="text" class="form-control" name="keyword" value="${search.keyword}"> <!-- 검색후 search(map객체)에서 가져옴. el구문 특성상 search가 없어도 오류 안나고 빈값으로 보임. -->
</div>
<button type="submit" class="search_btn btn btn-secondary">검색</button>
</form>
<c:if test="${ not empty search }">
<script>
$(document).ready(function(){
$("#search_form select").val('${search.condition}');
// 검색 후의 페이징바 클릭시 search_form을 강제로 submit(단, 페이지 번호는 현재 클릭한 페이지 번호로 바꿔서)
$("#paging_area a").on("click", function(){
let page = $(this).text(); // Previous | Next | 페이지번호
if(page == 'Previous'){
page = ${pi.currentPage - 1};
}else if(page == 'Next'){
page = ${pi.currentPage + 1};
}
$("#search_form input[name=page]").val(page);
$("#search_form").submit();
return false; // 기본이벤트(href='/board/list.do' url 요청)가 동작 안되도록 막는다. 이벤트가지고 pervent 어쩌구를 호출해서 막을 수도 있지만 이벤트 핸들러로
})
})
</script>
</c:if>
</div>
</section>
<!-- Section end -->
<!-- Footer start -->
<jsp:include page="/WEB-INF/views/common/footer.jsp"/>
<!-- Footer end -->
</div>
</body>
</html>
- 로그인버튼에 c:if
- a태그다. 버튼이 아니다. 부트스트랩 스타일이서서 버튼처럼 보일 뿐이다.
- a태그 href 작성. 바로 jsp로 못간다. 무조건 서블릿 요청해야 한다.
ㅁ board 폴더에 regist.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:set var="contextPath" value="${pageContext.request.contextPath}" />
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<div class="container p-3">
<!-- Header, Nav start -->
<jsp:include page="/WEB-INF/views/common/header.jsp"/>
<!-- Header, Nav end -->
<!-- Section start -->
<section class="row m-3" style="min-height: 500px">
<div class="container border p-5 m-4 rounded">
<h2 class="m-4">게시글 등록</h2>
<br>
<form id="enroll-form" method="post" action="${contextPath}/board/insert.do" enctype="multipart/form-data">
<div class="form-group">
<label for="title">제목 </label>
<input type="text" class="form-control" id="title" name="boardTitle" required><br>
<label for="writer">작성자 </label>
<input type="text" class="form-control" id="writer" value="${ loginUser.userId }" readonly><br> <!-- 어차피 db엔 회원번호를 기록해야함. 그래서 넘길필요 없다. -->
<label for="upfile">첨부파일 </label>
<input type="file" class="form-control-file border file" id="upfile" name="uploadFiles" multiple><br>
<label for="userName">내용 </label>
<textarea class="form-control" required name="boardContent" id="content" rows="10" style="resize:none;"></textarea><br>
</div>
<br>
<div align="center">
<button type="submit" class="btn btn-primary">등록하기</button>
<button type="reset" class="btn btn-danger">취소하기</button>
</div>
</form>
</div>
</section>
<!-- Section end -->
<!-- Footer start -->
<jsp:include page="/WEB-INF/views/common/footer.jsp"/>
<!-- Footer end -->
</div>
<script src="${ contextPath }/resources/js/fileValidate.js"></script>
</body>
</html>
- 게시판작성하기페이지_regist.html에서 body~body 복붙. 스타일은 없다.
- 그리고 value에 loginUser의 id가 보이게 했다.
- 첨부파일 class에 file 추가. multiple 추가.
- enctype="multipart/form-data"
ㅁ BoardController
package com.br.spring.controller;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpSession;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
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 org.springframework.web.servlet.mvc.support.RedirectAttributes;
import com.br.spring.dto.AttachDto;
import com.br.spring.dto.BoardDto;
import com.br.spring.dto.MemberDto;
import com.br.spring.dto.PageInfoDto;
import com.br.spring.service.BoardService;
import com.br.spring.util.FileUtil;
import com.br.spring.util.PagingUtil;
import lombok.RequiredArgsConstructor;
@RequestMapping("/board")
@RequiredArgsConstructor
@Controller
public class BoardController {
private final BoardService boardService;
private final PagingUtil pagingUtil;
private final FileUtil fileUtil;
// 메뉴 바에 있는 메뉴 클릭시 /board/list.do => 1번 페이지 요청
// 페이징 바에 있는 페이지 클릭시 /board/list.do?page=xx
@GetMapping("/list.do")
public void list(@RequestParam(value="page", defaultValue="1") int currentPage, Model model) {
int listCount = boardService.selectBoardListCount();
PageInfoDto pi = pagingUtil.getPageInfoDto(listCount, currentPage, 5, 5);
List<BoardDto> list = boardService.selectBoardList(pi);
model.addAttribute("pi", pi);
model.addAttribute("list", list);
// return "board/list"; 생략해도 됨.
}
@GetMapping("/search.do")
public String search(@RequestParam(value="page") int currentPage
, @RequestParam Map<String, String> search
, Model model) {
// 요청시 전달값을 매개변수를 둬서 받기
// 지금은 무조건 page라는 key값으로 1번이 올거라 defaultValue를 안썼다. 써도 된다.
// 요청하는 페이지 번호는 currentPage에 담긴다.
// 지금 condition과 keyword 값을 받아줄 dto 커맨드 객체가 없다. String 형 변수 2개를 둬서 받아도 된다. 근데 어차피 넘길때는 Map에 담아서 넘기도록 서비스 impl를 설계했었다.
// ajax때 했던 @RequestBody로 Map으로 바로 받을 수 있었다.
// 아니면 @RequestParam Map<String, String> search로 해도 된다. 이렇게 해본다. 알아서 key과 value값이 담긴다.
// Map<String, String> search => {condition=user_id|board_Title|board_content, keyword=란}
int listCount = boardService.selectSearchListCount(search);
PageInfoDto pi = pagingUtil.getPageInfoDto(listCount, currentPage, 5, 5);
List<BoardDto> list = boardService.selectSearchList(search, pi);
model.addAttribute("pi", pi);
model.addAttribute("list", list);
model.addAttribute("search", search); // list.jsp가 로드되는 경우가 2가지 경우다. /search.do라는 요청으로 로드될 때는 search라는 key값으로 Map이 담겨있다.
return "board/list"; // 이미 만든 list.jsp로 포워딩
}
@GetMapping("/regist.do")
public void registPage() {} // 메소드명은 별 의미 없다. 마음대로 작성.
@PostMapping("/insert.do")
public String regist(BoardDto board // 글제목, 글내용은 담겨있다.
, List<MultipartFile> uploadFiles // 첨부파일 개수만큼 MultipartFile 객체가 담긴다.
, HttpSession session // 로그인한 회원정보(회원번호)는 session에서 뽑아야 한다.
, RedirectAttributes rdAttributes) {
// board테이블에 insert할 데이터
board.setBoardWriter( String.valueOf( ((MemberDto)session.getAttribute("loginUser")).getUserNo() ) );
// 첨부파일 업로드 후에 attachment테이블에 insert할 데이터
List<AttachDto> attachList = new ArrayList<>();
for(MultipartFile file : uploadFiles) {
if(file != null && !file.isEmpty()) {
Map<String, String> map = fileUtil.fileupload(file, "board");
attachList.add(AttachDto.builder()
.filePath(map.get("filePath"))
.originalName(map.get("originalName"))
.filesystemName(map.get("filesystemName"))
.refType("B")
.build() );
}
}
board.setAttachList(attachList); // 제목, 내용, 작성자회원번호, 첨부파일들정보
int result = boardService.insertBoard(board);
if( attachList.isEmpty() && result == 1 || !attachList.isEmpty() && result == attachList.size() ) { // && (AND) 연산자가 || (OR) 연산자보다 우선순위가 높습니다.
rdAttributes.addFlashAttribute("alertMsg", "게시글 등록 성공");
}else {
rdAttributes.addFlashAttribute("alertMsg", "게시글 등록 실패");
}
return "redirect:/board/list.do";
}
}
- registPage 메소드 생성.
- FileUtil객체 전역변수로 추가.
ㅁ 서버 start
'Spring' 카테고리의 다른 글
[웹프로젝트] 16. 게시글 조회수 증가 (0) | 2024.10.31 |
---|---|
[웹프로젝트] 15. 게시글 상세 페이지 (1) | 2024.10.30 |
[웹프로젝트] 13. 게시판 검색 (0) | 2024.10.30 |
[웹프로젝트] 12. 게시판 목록 조회 (0) | 2024.10.29 |
[웹프로젝트] 11. 회원탈퇴 (0) | 2024.10.29 |