💎 Console
📢 location.pathname => '/board/1/2001'
📢 location.pathname.replace ("board", "board2") => '/board2/1/2001'
- replace : board => board2 로 바꾸는 함수
📢 location.pathname.replace ("board", "board2") + "/update" => '/board2/1/2001/update'
📢 location.search => '?cp=1'
🎈 최종으로 원하는 주소는 '/board2/1/2001/update?cp=1' 이기때문에
💎 Jsp
📢 enctype = "multipart/form-data" : 제출 데이터 인코딩 X
🔑 파일 제출은 가능
🔑 MultiPartResolver 가 문자열, 파일을 구분
🔑 문자열 -> String, int, DTO, Map ( HttpMessageConverter )
🔑 파일 -> MultiPartFile 객체 -> transferTo( ) ( 파일을 서버에 저장 )
💎 JS
👍Set 객체를 생성하는 이유 const deleteset = new Set() : 게시글 수정 시 삭제된 이미지의 순서를 기록하기 위해
✌️Set 특징 : 순서가 정해져있지않고 중복을 허용하지않는다 == x 버튼클릭시 순서를 딱 한번만 저장할때 용이함!!
😂 input type ="file"의 value 값은 오로지 "" (빈칸) 빈문자열만 대입가능!!
🎈 document querySelector("[name='deleteList']").value = Array.from(deleteSet)
-> ↑ jsp에있는 <input type ="hidden" name="deleteList" value=""> 의 value값에 리스트형식으로 값대입
-> Array.from( ) : Set -> Array 형식의 변경 () 에는 위에선언해둔 deleteSet 을 넣어준다!
📢 ★★ JS배열은 string에 대입되거나 출력될때 요소,요소,요소 형태의 문자열을 반환한다!! ★★
💎 Spring
- 스프링으로 요청주소를 보내줄때 js에서 페이지 이동을 위한 조치를 먼저해준다.
현재페이지 주소에서 ↓ 주소로 get방식 요청을 보낸다.
🔈locatio.pathname : URL에서 쿼리스트링전 즉 /board/1/1999 까지 얻어온다. 그후 replace를 이용하여
board -> board2 로 바꾸고 "/update" + location.search 를 해줘 URL 주소를 완성한다.
🔈locatio.search : URL 에서 쿼리스트링 부문을 얻어온다 ?cp=1
🔎 BoardController2
📣 Board board = boardService.selectBoard(map) -> 이 링크를 참고하세요!!
: BoardController에서 이미 조회한 상세조회를 이용 -> 수정시 기존에 작성된 제목,내용,사진등이 필요하기때문!!
// 게시글 수정 화면 전환
@GetMapping("/{boardCode}/{boardNo}/update")
public String boardUpdate(
@PathVariable("boardCode") int boardCode,
@PathVariable("boardNo") int boardNo,
Model model // 데이터 전달용 객체(기본 scope : request)
) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("boardCode", boardCode);
map.put("boardNo", boardNo);
Board board = boardService.selectBoard(map);
model.addAttribute("board", board);
// forward(요청 위임) -> request scope 유지
return "board/boardUpdate";
}
// 게시글 수정
@PostMapping("/{boardCode}/{boardNo}/update")
public String boardUpdate(
Board board // 커맨드 객체 (name == 필드 경우 필드에 파라미터 세팅) @ModelAttribute 생략가능
, @RequestParam(value="cp",required = false, defaultValue="1") int cp // 쿼리스트링 유지
, @RequestParam(value="deleteList", required=false) String deleteList // 삭제할 이미지순서
, @RequestParam(value="images", required = false) List<MultipartFile> images // 업로드된 파일 리스트
, @PathVariable("boardCode") int boardCode
, @PathVariable("boardNo") int boardNo
, HttpSession session // 서버 파일 저장 경로 얻어올 용도
, RedirectAttributes ra // 리다이렉트 시 값 전달용
) throws IllegalStateException, IOException {
// 1) boardCode, boardNo를 컨맨드 객체(board)에 세팅
board.setBoardCode(boardCode);
board.setBoardNo(boardNo);
// board(boardCode , boardNo, boardTitle, boardContent)
// 2) 이미지 서버 저장 경로, 웹 접근 경로
String webPath = "/resources/images/board";
String filePath = session.getServletContext().getRealPath(webPath);
// 3) 게시글 수정 서비스 호출
int rowCount = service.boardUpdate(board, images, webPath , filePath, deleteList);
// 4) 결과에 따라 message, path 설정
String message = null;
String path = "redirect:"; // redirect는 GET방식으로 요청처리
if(rowCount>0) {
message ="게시글이 수정되었습니다.";
path += "/board/" + boardCode + "/" + boardNo + "?cp=" + cp; // 상세조회 페이지
}else {
message = "게시글 수정 실패";
path += "update";
}
ra.addFlashAttribute("message",message);
return path;
}
🔎 BoardService2
🔎 BoardServiceImpl2
📣 XSS 방지처리 Util.XSSHandling -> 이 링크를 참고하세요!!
📣 이미지 삭제 실패시 전체 롤백을 위한 사영자정의 예외 처리 클래스 -> 이 링크를 참고하세요!!
// 게시글 수정 서비스
@Transactional(rollbackFor = Exception.class)
@Override
public int boardUpdate(Board board, List<MultipartFile> images, String webPath, String filePath, String deleteList) throws IllegalStateException, IOException {
// 1. 게시글 제목/내용만 수정
// 1) XSS 방지 처리
board.setBoardTitle(Util.XSSHandling(board.getBoardTitle()));
board.setBoardContent(Util.XSSHandling(board.getBoardContent()));
// 2) DAO 호출
int rowCount = dao.boardUpdate(board);
// 2. 게시글 부분이 수정 성공 했을 때
if(rowCount > 0 ) {
if(!deleteList.equals("") ) { // 삭제할 이미지가 있다면
// 3. deleteList에 작성된 이미지 모두 삭제
Map<String, Object> deleteMap = new HashMap<String, Object>();
deleteMap.put("boardNo", board.getBoardNo());
deleteMap.put("deleteList", deleteList);
rowCount = dao.imageDelete(deleteMap);
if(rowCount == 0 ) { // 이미지 삭제 실패 시 전체 롤백
// -> 예외 강제로 발생
throw new ImageDeleteException();
}
}
// 4. 새로 업로드된 이미지 분류 작업
// images : 실제 파일이 담긴 List
// -> input Type = "file" 개수만큼 요소가 존재
// -> 제출된 파일이 없어도 MultipartFile 객체가 존재
List<BoardImage> uploadList = new ArrayList<BoardImage>();
// images에 담겨있는 파일 중 실제 업로드된 파일만 분류
for (int i = 0; i < images.size(); i++) {
if (images.get(i).getSize() > 0) {
BoardImage img = new BoardImage();
// img에 파일 정보를 담아서 uploadList에 추가
img.setImagePath(webPath); // 웹 접근 경로
img.setBoardNo(board.getBoardNo()); // 게시글 번호
img.setImageOrder(i); // 이미지 순서
// 파일 원본명0
String fileName = images.get(i).getOriginalFilename();
img.setImageOriginal(fileName); // 원본명
img.setImageReName(Util.fileRename(fileName)); // 변경명
uploadList.add(img);
// 오라클은 다중UPDATE를 지원하지 않기 때문에
// 하나씩 UPDATE를 수행
rowCount = dao.imageUpdate(img);
if(rowCount == 0) {
// 수정 실패 == DB에 이미지가 없었다
// -> 이미지를 삽입
rowCount = dao.imageInsert(img);
}
}
}
// 5. uploadList에 있는 이미지들만 서버에 저장(transferTo())
if(!uploadList.isEmpty()) {
for(int i=0; i <uploadList.size(); i++) {
int index = uploadList.get(i).getImageOrder();
// 파일로 변환
String rename = uploadList.get(i).getImageReName();
images.get(index).transferTo( new File(filePath + rename));
}
}
}
return rowCount;
}
🔎 BoardDAO2
/** 게시글 수정
* @param board
* @return
*/
public int boardUpdate(Board board) {
return sqlSession.update("boardMapper.boardUpdate",board);
}
/** 이미지 삭제
* @param deleteMap
* @return rowCount
*/
public int imageDelete(Map<String, Object> deleteMap) {
return sqlSession.delete("boardMapper.imageDelete", deleteMap);
}
/** 이미지 수정
* @param img
* @return rowCount
*/
public int imageUpdate(BoardImage img) {
return sqlSession.update("boardMapper.imageUpdate",img);
}
/** 이미지 삽입(1개)
* @param img
* @return
*/
public int imageInsert(BoardImage img) {
return sqlSession.insert("boardMapper.imageInsert",img);
}
🔎 board-mapper
<!-- 게시글 수정 -->
<update id="boardUpdate">
UPDATE BOARD SET
BOARD_TITLE = #{boardTitle},
BOARD_CONTENT = #{boardContent},
B_UPDATE_DATE = SYSDATE
WHERE BOARD_NO = #{boardNo}
AND BOARD_CODE = #{boardCode}
</update>
<!-- 이미지 삭제 -->
<delete id="imageDelete">
DELETE FROM BOARD_IMG
WHERE BOARD_NO = #{boardNo}
AND IMG_ORDER IN ( ${deleteList} )
</delete>
<!-- 이미지 수정 -->
<update id="imageUpdate">
UPDATE BOARD_IMG SET
IMG_PATH = #{imagePath},
IMG_ORIGINAL = #{imageOriginal},
IMG_RENAME = #{imageReName}
WHERE BOARD_NO = #{boardNo}
AND IMG_ORDER = #{imageOrder}
</update>
<!-- 이미지 삽입-->
<insert id="imageInsert">
INSERT INTO BOARD_IMG
VALUES(SEQ_IMG_NO.NEXTVAL, #{imagePath}, #{imageReName},
#{imageOriginal},#{imageOrder},#{boardNo})
</insert>