[Framework] TIL 066 - 23.10.23

유진·2023년 10월 23일
0

07_Framework

게시글 수정

-> 기존에 쓰여져 있던 정보 토대로 게시글 수정하는 것

수정 버튼 클릭 시, 게시글 수정 가능하도록

boardDetail.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="fn" uri="http://java.sun.com/jsp/jstl/functions"  %>

<c:forEach items="${boardTypeList}" var="boardType">
    <c:if test="${boardType.BOARD_CODE == boardCode}" >
        <c:set var="boardName" value="${boardType.BOARD_NAME}"/>
    </c:if>
</c:forEach>

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>${boardName}</title>

    <link rel="stylesheet" href="/resources/css/board/boardDetail-style.css">
    <link rel="stylesheet" href="/resources/css/board/comment-style.css">

    <script src="https://kit.fontawesome.com/f7459b8054.js" crossorigin="anonymous"></script>
</head>
<body>
    <main>
        <jsp:include page="/WEB-INF/views/common/header.jsp"/>

        <section class="board-detail">  
            <!-- 제목 -->
            <h1 class="board-title">${board.boardTitle}  <span> - ${boardName}</span>    </h1>

            <!-- 프로필 + 닉네임 + 작성일 + 조회수 -->
            <div class="board-header">
                <div class="board-writer">

                    <!-- 프로필 이미지 -->
                    <c:choose>
                    	<%-- 프로필 이미지가 없을 경우 기본 이미지 출력 --%>
                    	<c:when test="${empty board.profileImage}">
		                    <img src="/resources/images/user.png">                    	
                    	</c:when>
                    	
                    	<%-- 프로필 이미지가 있을 경우 등록한 이미지 출력 --%>
                    	<c:otherwise>
                    		<img src="${board.profileImage}" />
                    	</c:otherwise>
                    
                    </c:choose>
                    
                	    

                    <span>${board.memberNickname}</span>

                    
                    <!-- 좋아요 하트 -->
                    <span class="like-area">
                    
                    	<%-- 좋아요 누른적이 없다, 로그인 하지 않았다 --%>
                    	<c:if test="${empty likeCheck}">
                        	<i class="fa-regular fa-heart" id="boardLike"></i>
						</c:if>
						
						<%-- 좋아요 누른적이 있다 --%>
						<c:if test="${not empty likeCheck}">
                        	<i class="fa-solid fa-heart" id="boardLike"></i>
                        </c:if>


                        <span>${board.likeCount}</span>
                    </span>

                </div>

                <div class="board-info">
                    <p> <span>작성일</span>${board.boardCreateDate}</p>     

                    <!-- 수정한 게시글인 경우 -->
                    <c:if test="${not empty board.boardUpdateDate}">
                    	<p> <span>마지막 수정일</span>${board.boardUpdateDate}</p>   
					</c:if>
					
                    <p> <span>조회수</span>${board.readCount}</p>                    
                </div>
            </div>

            <!-- 이미지가 있을 경우 -->
            <c:if test="${not empty board.imageList}">
            
	            <!-- 썸네일 영역(썸네일이 있을 경우) -->
	            <%--
	            	- 이미지는 IMG_ORDER 오름차순 정렬된다
	            	- IMG_ORDER의 값이 0인 이미지 썸네일이다
	            	-> imageList에 썸네일이 있다면
	            		조회되었을때 IMG_ORDER가 0인 이미지가
	            		imageList[0]에 저장되어 있을 것이다.
	             --%>
	             <c:if test="${board.imageList[0].imageOrder == 0}">
		            <h5>썸네일</h5>
		            <div class="img-box">
		                <div class="boardImg thumbnail">
		                    <img src="${board.imageList[0].imagePath}${board.imageList[0].imageReName}">
		                    <a href="${board.imageList[0].imagePath}${board.imageList[0].imageReName}" 
		                    	download="${board.imageList[0].imageOriginal}"
		                    >다운로드</a>         
		                </div>
		            </div>
	             </c:if>
	             
            </c:if>
            
            <%-- 썸네일이 있을 경우 --%>
            <c:if test="${board.imageList[0].imageOrder == 0}">
            	<c:set var="start" value="1" />
            </c:if>
            
            <%-- 썸네일이 없을 경우 --%>
        	<c:if test="${board.imageList[0].imageOrder != 0}">
            	<c:set var="start" value="0" />
            </c:if>

			
			<%-- fn:length(board.imageList) : imageList의 길이 반환 --%>
            <!-- 일반 이미지가 있는 경우 -->
            <c:if test="${fn:length(board.imageList) > start}">
            
	            <!-- 업로드 이미지 영역 -->
	            <h5>업로드 이미지</h5>
	            <div class="img-box">
	                
	                <c:forEach var="i" begin="${start}" end="${fn:length(board.imageList) - 1}">
		                <div class="boardImg">
		                
		                	<c:set var="path"
		                		value="${board.imageList[i].imagePath}${board.imageList[i].imageReName}" />
			 				               
				                
		                    <img src="${path}">
		                    <a href="${path}" download="${board.imageList[i].imageOriginal}">다운로드</a>                
		                </div>
	                </c:forEach>
	                
	
	            </div>
            
            </c:if>
            

            <!-- 내용 (cf) white-space: pre-wrap; 설정이 되어 있어서 div 안 공백이 있어선 안됨. -->
            <div class="board-content">${board.boardContent}</div>


            <!-- 버튼 영역-->
            <div class="board-btn-area">
            
            	<!-- 로그인한 회원과 게시글 작성자 번호가 같은 경우 -->
            	<c:if test="${loginMember.memberNo == board.memberNo}">
	                <button id="updateBtn">수정</button>
	                <button id="deleteBtn">삭제</button>
            	</c:if>


                <button id="goToListBtn">목록으로</button>
            </div>


        </section>

        <!-- 댓글 include-->
        <jsp:include page="comment.jsp"/>
    </main>

    <jsp:include page="/WEB-INF/views/common/footer.jsp"/>

	<script>
		// JSP에서 작성 가능한 언어/라이브러리
		// -> html, css, js, java, EL, JSTL
		
		// JSP 해석 우선 순위 : Java/EL/JSTL > HTML,CSS,JS
		
		const boardNo = "${board.boardNo}"
		
		const loginMemberNo = "${loginMember.memberNo}"
	
		console.log(boardNo);
		console.log(loginMemberNo);
	
	</script>
	
	<script src="/resources/js/board/boardDetail.js"></script>
	
</body>
</html>

boardDetail.js

// 좋아요 버튼이 클릭 되었을 때
const boardLike = document.getElementById("boardLike");

boardLike.addEventListener("click", e => {

    // 로그인 여부 검사
    if(loginMemberNo == ""){
        alert("로그인 후 이용해주세요")
        return;
    }

    let check; // 기존에  좋아요 X(빈하트) : 0  
               //         좋아요 O(꽉찬하트) : 1

    // contains("클래스명") : 클래스가 있으면 true, 없으면 false
    if(e.target.classList.contains("fa-regular")){ // 좋아요 X(빈하트)
        check = 0;
    }else{ // 좋아요 O(꽉찬하트) 
        check = 1;
    }

    // ajax로 서버로 제출할 파라미터를 모아둔 JS 객체
    const data =   {"boardNo" : boardNo , 
                    "memberNo" : loginMemberNo,
                    "check" : check };

    // ajax 코드 작성
    fetch("/board/like", {
        method : "POST",
        headers : {"Content-Type" : "application/json"},
        body : JSON.stringify(data)
    })              // 자료형 int 1개여서 text()로 파싱
    .then(response => response.text()) // 응답 객체를 필요한 형태로 파싱하여 리턴

    .then(count => { 

        console.log("count : " + count);

        if(count == -1){ // INSERT, DELETE 실패 시
            console.log("좋아요 처리 실패");
            return;
        }

        // toggle() : 클래스가 있으면 없애고, 없으면 추가하고
        e.target.classList.toggle("fa-regular");
        e.target.classList.toggle("fa-solid");

        // 현재 게시글의 좋아요 수를 화면에 출력
        e.target.nextElementSibling.innerText = count;


    }) // 파싱된 데이터를 받아서 처리하는 코드 작성

    .catch(err => {
        console.log("예외 발생");
        console.log(err);
    }) // 예외 발생 시 처리하는 부분


});


// 게시글 수정 버튼 클릭 시
document.getElementById("updateBtn").addEventListener("click", () => {
	
	location.href
		= location.pathname.replace("board", "board2")
			+ "/update"
			+ location.search
			
		// /board2/2/2006/update?cp=1 (GET)

});

BoardController2.java

package edu.kh.project.board.controller;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpSession;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
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.bind.annotation.SessionAttribute;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import edu.kh.project.board.model.dto.Board;
import edu.kh.project.board.model.service.BoardService;
import edu.kh.project.board.model.service.BoardService2;
import edu.kh.project.member.model.dto.Member;

@Controller
@RequestMapping("/board2")
@SessionAttributes({"loginMember"})
public class BoardController2 {
	
	@Autowired
	private BoardService2 service; // 삽입,수정,삭제
	
	@Autowired
	private BoardService boardService; // 목록, 상세 조회
	
	
	// 게시글 작성 화면 전환
	@GetMapping("/{boardCode:[0-9]+}/insert")
	public String boardInsert(@PathVariable("boardCode") int boardCode) {
		// @PathVariable : 주소 값 가져오기 + request scope에 값 올리기
		
		return "board/boardWrite";
	}
	
	// 게시글 작성
	@PostMapping("/{boardCode:[0-9]+}/insert")
	public String boardInsert(
			@PathVariable("boardCode") int boardCode
			, Board board // 커맨드 객체 (필드에 파라미터 담겨있음!)
			, @RequestParam(value="images", required = false) List<MultipartFile> images
			, @SessionAttribute("loginMember") Member loginMember
			, RedirectAttributes ra
			, HttpSession session
			) throws IllegalStateException, IOException {
	
		// 파라미터 : 제목, 내용, 파일(0~5개)
		// 파일 저장 경로 : HttpSession
		// 세션 : 로그인한 회원의 번호
		// 리다이렉트 시 데이터 전달 : RedirectAttributes ra (message)
		
		/* List<MultipartFile>
		 * - 업로드된 이미지가 없어도 List에 MultipartFile 요소는 존재함.
		 * 
		 * - 단, 업로드된 이미지가 없는 MultipartFile 요소는
		 * 	 파일 크기(size)가 0 또는 파일명(getOriginalFileName()) "" 빈칸
		 * 
		 * */
		
		// 1. 로그인한 회원 번호를 얻어와 board에 세팅
		board.setMemberNo(loginMember.getMemberNo());
		
		// 2. boardCode도 board에 세팅
		board.setBoardCode(boardCode);
		
		
		// 3. 업로드 된 이미지 서버에 실제로 저장되는 경로
		//    + 웹에서 요청 시 이미지를 볼 수 있는 경로 (웹 접근 경로)
		String webPath = "/resources/images/board/";
		String filePath = session.getServletContext().getRealPath(webPath);
		
		// 게시글 삽입 서비스 호출 후 삽입된 게시글 번호 반환 받기
		int boardNo = service.boardInsert(board, images, webPath, filePath);
		
		
		// 게시글 삽입 성공 시
		// -> 방금 삽입한 게시글의 상세 조회 페이지 리다이렉트
		// -> /board/{boardCode}/{boardNo}
		
		String message = null;
		String path = "redirect:";
		
		if(boardNo > 0) {
			message = "게시글이 등록 되었습니다";
			path += "/board/" + boardCode + "/" + boardNo;
		} else {
			message = "게시글 등록 실패................";
			path += "insert";
		}
		
		ra.addFlashAttribute("message", message);
		
		return path;
		
	}
	
	// 게시글 수정 화면 전환
	@GetMapping("/{boardCode}/{boardNo}/update")   // /board2/2/2006/update?cp=1
	public String boardUpdate(
			@PathVariable("boardCode") int boardCode,
			@PathVariable("boardNo") int boardNo,
			Model 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);
		
		return "board/boardUpdate";
		
	}
	
	

}

boardUpdate.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="fn" uri="http://java.sun.com/jsp/jstl/functions"  %>

<c:forEach items="${boardTypeList}" var="boardType">
    <c:if test="${boardType.BOARD_CODE == boardCode}" >
        <c:set var="boardName" value="${boardType.BOARD_NAME}"/>
    </c:if>
</c:forEach>

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>${boardName}</title>

    <link rel="stylesheet" href="/resources/css/board/boardWrite-style.css">
</head>
<body>
    <main>
        <jsp:include page="/WEB-INF/views/common/header.jsp"/>

        <!--
        상대경로
        /board2/1/2006/update?cp=1  (GET)
        /board2/1/2006/update (POST) 
        -->
        <form action="update" method="POST" 
            class="board-write" id="boardUpdateFrm" enctype="multipart/form-data">  
            <%-- enctype="multipart/form-data : 제출 데이터 인코딩 X
                    -> 파일 제출 가능 
                    -> MultiPartResolver가 문자열, 파일을 구분
                    --> 문자열 -> String, int , DTO, Map (HttpMessageConverter)
                    --> 파일 -> MultiPartFile 객체 -> transferTo() (파일을 서버에 저장)
            --%>

            <h1 class="board-name">${boardName}</h1>

            <!-- 제목 -->
            <h1 class="board-title">
                <input type="text" name="boardTitle" placeholder="제목" value="${board.boardTitle}">   
            </h1>


            <%--  
                board.imageList에 존재하는 이미지 객체를 얻어와
                순서(imageOrder) 별로 변수 생성
            --%>

            <c:forEach items="${board.imageList}" var="img">
                <c:choose>
                    <c:when test="${img.imageOrder == 0}">
                        <c:set var="img0" value="${img.imagePath}${img.imageReName}"/>
                    </c:when>

                    <c:when test="${img.imageOrder == 1}">
                        <c:set var="img1" value="${img.imagePath}${img.imageReName}"/>
                    </c:when>

                    <c:when test="${img.imageOrder == 2}">
                        <c:set var="img2" value="${img.imagePath}${img.imageReName}"/>
                    </c:when>

                    <c:when test="${img.imageOrder == 3}">
                        <c:set var="img3" value="${img.imagePath}${img.imageReName}"/>
                    </c:when>

                    <c:when test="${img.imageOrder == 4}">
                        <c:set var="img4" value="${img.imagePath}${img.imageReName}"/>
                    </c:when>
                </c:choose>
            </c:forEach>


            <!-- 썸네일 영역 -->
            <h5>썸네일</h5>
            <div class="img-box">
                <div class="boardImg thumbnail">
                    <label for="img0">
                        <img class="preview" src="${img0}">
                    </label>
                    <input type="file" name="images" class="inputImage" id="img0" accept="image/*">
                    <span class="delete-image">&times;</span>
                </div>
            </div>


            <!-- 업로드 이미지 영역 -->
            <h5>업로드 이미지</h5>
            <div class="img-box">

                <div class="boardImg">
                    <label for="img1">
                        <img class="preview" src="${img1}">
                    </label>
                    <input type="file" name="images" class="inputImage" id="img1" accept="image/*">
                    <span class="delete-image">&times;</span>
                </div>

                <div class="boardImg">
                    <label for="img2">
                        <img class="preview" src="${img2}">
                    </label>
                    <input type="file" name="images" class="inputImage" id="img2" accept="image/*">
                    <span class="delete-image">&times;</span>
                </div>

                <div class="boardImg">
                    <label for="img3">
                        <img class="preview" src="${img3}">
                    </label>
                    <input type="file" name="images" class="inputImage" id="img3" accept="image/*">
                    <span class="delete-image">&times;</span>
                </div>

                <div class="boardImg">
                    <label for="img4">
                        <img class="preview" src="${img4}">
                    </label>
                    <input type="file" name="images" class="inputImage" id="img4" accept="image/*">
                    <span class="delete-image">&times;</span>
                </div>
            </div>

            <!-- 내용 -->
            <div class="board-content">
                <textarea name="boardContent">${board.boardContent}</textarea>
            </div>


            <!-- 버튼 영역 -->
            <div class="board-btn-area">
                <button type="submit" id="writebtn">등록</button>
            </div>

            
            <!-- 기존 이미지가 있다가 삭제된 이미지의 순서를 기록-->
            <input type="hidden" name="deleteList" value="">

            <%-- 수정 성공 시 주소(쿼리스트링) 유지용도 --%>
            <input type="hidden" name="cp" value="${param.cp}">
        </form>

    </main>

    <jsp:include page="/WEB-INF/views/common/footer.jsp"/>

    <script src="/resources/js/board/boardUpdate.js"></script>

</body>
</html>


수정 버튼 클릭 시, 해당 페이지로 이동


  • 3가지 경우를 다 고려해야 함.
    1) 사진 있다 -> 변경 -> DB UPDATE
    2) 사진 없다 -> 있다 -> DB INSERT
    3) 사진 있다 -> 없다 -> DB DELETE

EX) input type="file" "비밀번호 보안 파일"

선택된 파일 없음 -> 변경사항 없음

(선택된 파일 없음 = 삭제 O)

삭제한 사진 따로 기록해둘 것 input type="hidden"


boardUpdate.js

// 미리보기 관련 요소 모두 얻어오기

// img 5개
const preview = document.getElementsByClassName("preview"); 

// file 5개
const inputImage = document.getElementsByClassName("inputImage"); 

// x버튼 5개
const deleteImage = document.getElementsByClassName("delete-image"); 
// -> 위에 얻어온 요소들의 개수가 같음 == 인덱스가 일치함



// 게시글 수정 시 삭제된 이미지의 순서를 기록할 Set 객체 생성
const deleteSet = new Set(); // 순서x, 중복x
// -> X버튼 클릭 시 순서를 한 번만 저장하는 용도



for(let i=0 ; i< inputImage.length ; i++){

    // 파일이 선택되거나, 선택 후 취소 되었을 때
    inputImage[i].addEventListener('change', e => {

        const file = e.target.files[0]; // 선택된 파일의 데이터

        if(file != undefined){ // 파일이 선택 되었을 때

            const reader = new FileReader(); // 파일을 읽는 객체

            reader.readAsDataURL(file);
            // 지정된 파일을 읽은 후 result 변수에 URL 형식으로 저장

            reader.onload = e => { // 파일을 다 읽은 후 수행
                preview[i].setAttribute("src", e.target.result);

                // 이미지가 성공적으로 읽어지면
                // deleteSet에서 삭제
                deleteSet.delete(i);
            }

        } else { // 선택 후 취소 되었을 때
                // -> 선택된 파일 없음 -> 미리보기 삭제
            preview[i].removeAttribute("src");
        }
    });


    // 미리보기 삭제 버튼(X버튼)
    deleteImage[i].addEventListener('click', () => {

        // 미리보기 이미지가 있을 경우
        if(preview[i].getAttribute("src") != ""){

            // 미리보기 삭제
            preview[i].removeAttribute("src");

            // input type="file" 태그의 value를 삭제
            // **  input type="file" 의 value는 ""(빈칸)만 대입 가능 **
            inputImage[i].value = "";  


            // deleteSet에 삭제된 이미지 순서(i) 추가
            deleteSet.add(i);
        }

    });
}


// 게시글 수정 시 제목, 내용 작성 여부 검사
const boardUpdateFrm = document.querySelector("#boardUpdateFrm");
const boardTitle = document.querySelector("[name='boardTitle']");
const boardContent = document.querySelector("[name='boardContent']");


boardUpdateFrm.addEventListener('submit', e => {

    if(boardTitle.value.trim().length == 0){
        alert("제목을 입력해주세요")
        boardTitle.value = "";
        boardTitle.focus();
        e.preventDefault(); // form 기본 이벤트 제거
        return;
    }

    if(boardContent.value.trim().length == 0){
        alert("내용을 입력해주세요")
        boardContent.value = "";
        boardContent.focus();
        e.preventDefault(); // form 기본 이벤트 제거
        return;
    }


    // input type="hidden" 태그에
    // deleteSet에 저장된 값을 "1,2,3" 형태로 변경해서 저장

    // Array.from(deleteSet) : Set -> Array 변경

    // JS 배열은 string에 대입되거나 출력될 때
    // 요소,요소,요소 형태의 문자열을 반환한다! 

    document.querySelector("[name='deleteList']").value 
        =  Array.from(deleteSet);

    //e.preventDefault(); // 확인만하고 지울 예정
});

BoardController2.java

package edu.kh.project.board.controller;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpSession;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
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.bind.annotation.SessionAttribute;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import edu.kh.project.board.model.dto.Board;
import edu.kh.project.board.model.service.BoardService;
import edu.kh.project.board.model.service.BoardService2;
import edu.kh.project.member.model.dto.Member;

@Controller
@RequestMapping("/board2")
@SessionAttributes({"loginMember"})
public class BoardController2 {
	
	@Autowired
	private BoardService2 service; // 삽입,수정,삭제
	
	@Autowired
	private BoardService boardService; // 목록, 상세 조회
	
	
	// 게시글 작성 화면 전환
	@GetMapping("/{boardCode:[0-9]+}/insert")
	public String boardInsert(@PathVariable("boardCode") int boardCode) {
		// @PathVariable : 주소 값 가져오기 + request scope에 값 올리기
		
		return "board/boardWrite";
	}
	
	// 게시글 작성
	@PostMapping("/{boardCode:[0-9]+}/insert")
	public String boardInsert(
			@PathVariable("boardCode") int boardCode
			, Board board // 커맨드 객체 (필드에 파라미터 담겨있음!)
			, @RequestParam(value="images", required = false) List<MultipartFile> images
			, @SessionAttribute("loginMember") Member loginMember
			, RedirectAttributes ra
			, HttpSession session
			) throws IllegalStateException, IOException {
	
		// 파라미터 : 제목, 내용, 파일(0~5개)
		// 파일 저장 경로 : HttpSession
		// 세션 : 로그인한 회원의 번호
		// 리다이렉트 시 데이터 전달 : RedirectAttributes ra (message)
		
		/* List<MultipartFile>
		 * - 업로드된 이미지가 없어도 List에 MultipartFile 요소는 존재함.
		 * 
		 * - 단, 업로드된 이미지가 없는 MultipartFile 요소는
		 * 	 파일 크기(size)가 0 또는 파일명(getOriginalFileName()) "" 빈칸
		 * 
		 * */
		
		// 1. 로그인한 회원 번호를 얻어와 board에 세팅
		board.setMemberNo(loginMember.getMemberNo());
		
		// 2. boardCode도 board에 세팅
		board.setBoardCode(boardCode);
		
		
		// 3. 업로드 된 이미지 서버에 실제로 저장되는 경로
		//    + 웹에서 요청 시 이미지를 볼 수 있는 경로 (웹 접근 경로)
		String webPath = "/resources/images/board/";
		String filePath = session.getServletContext().getRealPath(webPath);
		
		// 게시글 삽입 서비스 호출 후 삽입된 게시글 번호 반환 받기
		int boardNo = service.boardInsert(board, images, webPath, filePath);
		
		
		// 게시글 삽입 성공 시
		// -> 방금 삽입한 게시글의 상세 조회 페이지 리다이렉트
		// -> /board/{boardCode}/{boardNo}
		
		String message = null;
		String path = "redirect:";
		
		if(boardNo > 0) {
			message = "게시글이 등록 되었습니다";
			path += "/board/" + boardCode + "/" + boardNo;
		} else {
			message = "게시글 등록 실패................";
			path += "insert";
		}
		
		ra.addFlashAttribute("message", message);
		
		return path;
		
	}
	
	// 게시글 수정 화면 전환
	@GetMapping("/{boardCode}/{boardNo}/update")   // /board2/2/2006/update?cp=1
	public String boardUpdate(
			@PathVariable("boardCode") int boardCode,
			@PathVariable("boardNo") int boardNo,
			Model 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);
		
		return "board/boardUpdate";
		
	}
	
	// 게시글 수정
	@PostMapping("/{boardCode}/{boardNo}/update")
	public String boardUpdate(
			Board board, // 커맨드 객체(name == 필드 경우 필드에 파라미터 세팅)
			@PathVariable("boardCode") int boardCode,
			@PathVariable("boardNo") int boardNo,
			@RequestParam(value="cp", required = false, defaultValue = "1") int cp, // 쿼리스트링 유지
			@RequestParam(value="images", required = false) List<MultipartFile> images, // 업로드된 파일 리스트
			@RequestParam(value="deleteList", required = false) String deleteList, // 삭제할 이미지 순서
			HttpSession session, // 서버 파일 저장 경로 얻어올 용도
			RedirectAttributes ra // 리다이렉트 시 값 전달용(message)
			) 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:";
		
		
		if(rowCount > 0) {
			message = "게시글이 수정되었습니다";
			path += "/board/" + boardCode + "/" + boardNo + "?cp=" + cp;
		} else {
			message = "게시글 수정 실패";
			path += "update";
		}
		
		ra.addFlashAttribute("message", message);
		
		return path;
	}
	
	
	

}

BoardService2.java

package edu.kh.project.board.model.service;

import java.io.IOException;
import java.util.List;

import org.springframework.web.multipart.MultipartFile;

import edu.kh.project.board.model.dto.Board;

public interface BoardService2 {

	/** 게시글 삽입
	 * @param board
	 * @param images
	 * @param webPath
	 * @param filePath
	 * @return boardNo
	 */
	int boardInsert(Board board, List<MultipartFile> images,
								String webPath, String filePath)
								throws IllegalStateException, IOException;

	/** 게시글 수정 서비스
	 * @param board
	 * @param images
	 * @param webPath
	 * @param filePath
	 * @param deleteList
	 * @return rowCount
	 */
	int boardUpdate(Board board, List<MultipartFile> images,
			String webPath, String filePath, String deleteList) throws IllegalStateException, IOException;

}

BoardServiceImpl2.java

package edu.kh.project.board.model.service;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import edu.kh.project.board.model.exception.FileUploadException;
import edu.kh.project.board.model.exception.ImageDeleteException;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import edu.kh.project.board.model.dao.BoardDAO2;
import edu.kh.project.board.model.dto.Board;
import edu.kh.project.board.model.dto.BoardImage;
import edu.kh.project.common.utility.Util;

@Service
public class BoardServiceImpl2 implements BoardService2{

	@Autowired
	private BoardDAO2 dao;

	// 게시글 삽입
	@Transactional(rollbackFor = Exception.class)
	@Override
	public int boardInsert(Board board, List<MultipartFile> images, String webPath, String filePath) 
			throws IllegalStateException, IOException {
		
		// 0. XSS 방지 처리
		board.setBoardContent( Util.XSSHandling( board.getBoardContent() ) /*XSS 방지 처리를 한 내용*/ );
		board.setBoardTitle( Util.XSSHandling( board.getBoardTitle() )/*XSS 방지 처리를 한 내용*/ );
		
		
		// 1. BOARD 테이블 INSERT 하기 (제목, 내용, 작성자, 게시판 코드)
		// -> boardNo (시퀀스로 생성한 번호) 반환 받기
		int boardNo = dao.boardInsert(board);
		
		
		// 2. 게시글 삽입 성공 시
		// 업로드된 이미지가 있다면 BOARD_IMG 테이블에 삽입하는 DAO 호출
		if(boardNo > 0) { // 게시글 삽입 성공 시
			
			// List<MultipartFile> images
			// -> 업로드된 파일이 담긴 객체 MultipartFile이 5개 존재
			// -> 단, 업로드된 파일이 없어도 MultipartFile 객체는 존재
			
			// 실제 업로드된 파일의 정보를 기록할 List
			List<BoardImage> uploadList = new ArrayList<BoardImage>();
			
			// images에 담겨있는 파일 중 실제 업로드된 파일만 분류
			for(int i = 0; i < images.size(); i++) {
				
				// i번째 요소에 업로드된 파일이 있다면
				if(images.get(i).getSize() > 0) {
					
					BoardImage img = new BoardImage();
					
					// img 에 파일 정보를 담아서 uploadList 에 추가
					img.setImagePath(webPath); // 웹 접근 경로
					img.setBoardNo(boardNo); // 게시글 번호
					img.setImageOrder(i); // 이미지 순서

					// 파일 원본명
					String fileName = images.get(i).getOriginalFilename();
					
					img.setImageOriginal(fileName); // 원본명
					img.setImageReName( Util.fileRename(fileName) ); // 변경명
					
					
					uploadList.add(img);
					
				}
				
			} // 분류 for 문 종료
			
			
			// 분류 작업 후 uploadList가 비어 있지 않은 경우
			// == 업로드한 파일이 있다
			if( !uploadList.isEmpty() ) {
				
				// BOARD_IMG 테이블에 insert 하는 dao 호출
				int result = dao.insertImageList(uploadList);
				// result == 삽입된 행의 개수
				
				// 전체 insert 성공 여부 확인
				// 삽입된 행의 개수와 uploadList 의 개수가 같다면
				// == 전체 insert 성공
				if(result == uploadList.size()) {
					
					// 서버에 파일을 저장 (transferTo)
					
					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) );
						
					}
					
					
					
				} else { // 일부 또는 전체 insert 실패
					
					// rollback 필요
					
					// @Transactional(rollbackFor = Exception.class)
					// -> 예외가 발생 해야지만 롤백
					
					// [결론]
					// 예외를 강제 발생 시켜서 rollback 해야된다
					// 사용자 정의 예외 만들어 발생시키자!
					
					
					throw new FileUploadException();
				}
				
			}
			
		}
		
		
		return boardNo;
	}

	// 게시글 수정 서비스
	@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<>();
			
			for(int i=0 ; i<images.size(); i++) {
				
				if(images.get(i).getSize() > 0) { // 업로드된 파일이 있을 경우
					
					// BoardImage 객체를 만들어 값 세팅 후 
					// uploadList에 추가
					BoardImage img = new BoardImage();
					
					// img에 파일 정보를 담아서 uploadList에 추가
					img.setImagePath(webPath); // 웹 접근 경로
					img.setBoardNo(board.getBoardNo()); // 게시글 번호
					img.setImageOrder(i); // 이미지 순서
					
					// 파일 원본명
					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.java

package edu.kh.project.board.model.dao;

import java.util.List;
import java.util.Map;

import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import edu.kh.project.board.model.dto.Board;
import edu.kh.project.board.model.dto.BoardImage;

@Repository
public class BoardDAO2 {

	@Autowired
	private SqlSessionTemplate sqlSession;

	/** 게시글 삽입
	 * @param board
	 * @return boardNo
	 */
	public int boardInsert(Board board) {
		
		int result = sqlSession.insert("boardMapper.boardInsert", board);
		
		// 삽입 성공 시
		if(result > 0) result = board.getBoardNo();
		
		return result;
	}

	/** 이미지 리스트 삽입
	 * @param uploadList
	 * @return result
	 */
	public int insertImageList(List<BoardImage> uploadList) {
		return sqlSession.insert("boardMapper.insertImageList", uploadList);
	}

	/** 게시글 수정
	 * @param board
	 * @return rowCount
	 */
	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
	 */
	public int imageUpdate(BoardImage img) {
		return sqlSession.update("boardMapper.imageUpdate", img);
	}

	/** 이미지 삽입
	 * @param img
	 * @return rowCount
	 */
	public int imageInsert(BoardImage img) {
		return sqlSession.insert("boardMapper.imageInsert", img);
	}
}

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">


	<!-- Board DTO에 대한 resultMap -->
	<resultMap type="Board" id="board_rm">
	
		<id property="boardNo" column="BOARD_NO"/>
		
		<result property="boardTitle" column="BOARD_TITLE"/>
		<result property="boardContent" column="BOARD_CONTENT"/>
		<result property="boardCreateDate" column="B_CREATE_DATE"/>
		<result property="boardUpdateDate" column="B_UPDATE_DATE"/>
		<result property="readCount" column="READ_COUNT"/>
		<result property="commentCount" column="COMMENT_COUNT"/>
		<result property="likeCount" column="LIKE_COUNT"/>
		<result property="memberNickname" column="MEMBER_NICKNAME"/>
		
		<result property="memberNo" column="MEMBER_NO"/>
		<result property="profileImage" column="PROFILE_IMG"/>
		
		<result property="thumbnail" column="THUMBNAIL"/>
		
		<!-- collection 태그 
			select로 조회된 결과를 컬렉션(List)에 담아
			지정된 필드에 세팅
			
			property : List를 담을 DTO의 필드명
			select : 실행할 select의 id
			column : 조회 결과 중 지정된 컬럼의 값을 파라미터로 전달
			javaType : List(컬렉션)의 타입을 지정
			ofType : List(컬렉션)의 제네릭(타입 제한) 지정
		-->
		<collection property="imageList" 
					select="selectImageList"
					column="BOARD_NO"
					javaType="java.util.ArrayList"
					ofType="BoardImage"
		/>
		
		<collection property="commentList" 
					select="selectCommentList"
					column="BOARD_NO"
					javaType="java.util.ArrayList"
					ofType="Comment"
		/>
	</resultMap>
	
	
	<!-- BoardImage resultMap -->
	<resultMap type="BoardImage" id="boardImage_rm">
		<id property="imageNo" column="IMG_NO"/>
		
		<result property="imagePath" column="IMG_PATH"/>
		<result property="imageReName" column="IMG_RENAME"/>
		<result property="imageOriginal" column="IMG_ORIGINAL"/>
		<result property="imageOrder" column="IMG_ORDER"/>
		<result property="boardNo" column="BOARD_NO"/>
	</resultMap>
	
	
	<!-- Comment resultMap -->
	<resultMap type="Comment" id="comment_rm">
		<id property="commentNo" column="COMMENT_NO"/>
		<result property="commentContent" column="COMMENT_CONTENT"/>
		<result property="commentCreateDate" column="C_CREATE_DATE"/>
		<result property="boardNo" column="BOARD_NO"/>
		<result property="memberNo" column="MEMBER_NO"/>
		<result property="commentDeleteFlag" column="COMMENT_DEL_FL"/>
		<result property="parentNo" column="PARENT_NO"/>
		<result property="memberNickname" column="MEMBER_NICKNAME"/>
		<result property="profileImage" column="PROFILE_IMG"/>
	</resultMap>
	
	

	<!-- 
		resultType이 "map"인 경우
		K : 컬럼명(BOARD_CODE, BOARD_NAME)
		V : 컬럼 값(	  1	   ,   공지 사항  )
		
		[{BOARD_NAME= 공지사항, BOARD_CODE=1}, {BOARD_NAME= 자유게시판, BOARD_CODE=2}, ...]
	 -->

	<!-- 게시판 종류 목록 조회 -->
	<select id="selectBoardTypeList" resultType="map">
		SELECT * FROM "BOARD_TYPE" ORDER BY 1
	</select>
	
	<!-- 특정 게시판의 삭제되지 않은 게시글 수 조회 -->
	<select id="getListCount" resultType="_int">
		SELECT COUNT(*) FROM BOARD
		WHERE BOARD_DEL_FL = 'N'
		AND BOARD_CODE = #{boardCode}
	</select>

	<!-- CDATA 태그 : 해당 태그 내부에 작성된 것은 모두 문자로 취급 -->
	<select id="selectBoardList" resultMap="board_rm">
		SELECT BOARD_NO, BOARD_TITLE, MEMBER_NICKNAME, READ_COUNT,
		
			<![CDATA[
				CASE
					WHEN SYSDATE - B_CREATE_DATE < 1/24/60
					THEN FLOOR( (SYSDATE - B_CREATE_DATE) * 24 * 60 * 60 ) || '초 전'
					WHEN SYSDATE - B_CREATE_DATE < 1/24
					THEN FLOOR( (SYSDATE - B_CREATE_DATE) * 24 * 60) || '분 전'
					WHEN SYSDATE - B_CREATE_DATE < 1
					THEN FLOOR( (SYSDATE - B_CREATE_DATE) * 24) || '시간 전'
					ELSE TO_CHAR(B_CREATE_DATE, 'YYYY-MM-DD')
				END B_CREATE_DATE,
			]]>
			(SELECT COUNT(*) FROM "COMMENT" C
			 WHERE C.BOARD_NO = B.BOARD_NO) COMMENT_COUNT,
			(SELECT COUNT(*) FROM BOARD_LIKE L
			 WHERE L.BOARD_NO = B.BOARD_NO) LIKE_COUNT,
			(SELECT IMG_PATH || IMG_RENAME FROM BOARD_IMG I
			WHERE I.BOARD_NO = B.BOARD_NO
			AND IMG_ORDER = 0) THUMBNAIL
		FROM "BOARD" B
		JOIN "MEMBER" USING(MEMBER_NO)
		WHERE BOARD_DEL_FL = 'N'
		AND BOARD_CODE = #{boardCode}
		ORDER BY BOARD_NO DESC
	</select>
	
	
	<select id="selectBoard" resultMap="board_rm">
		SELECT BOARD_NO, BOARD_TITLE, BOARD_CONTENT, BOARD_CODE,
			READ_COUNT, MEMBER_NICKNAME, MEMBER_NO, PROFILE_IMG,
			TO_CHAR(B_CREATE_DATE, 'YYYY"년" MM"월" DD"일" HH24:MI:SS') B_CREATE_DATE,
			TO_CHAR(B_UPDATE_DATE, 'YYYY"년" MM"월" DD"일" HH24:MI:SS') B_UPDATE_DATE,
			(SELECT COUNT(*)
			FROM "BOARD_LIKE" L
			WHERE L.BOARD_NO = B.BOARD_NO) LIKE_COUNT
		FROM "BOARD" B
		JOIN "MEMBER" USING(MEMBER_NO)
		WHERE BOARD_DEL_FL = 'N'
		AND BOARD_CODE = #{boardCode}
		AND BOARD_NO = #{boardNo}
	</select>
	
	<!-- 특정 게시글 이미지 조회 -->
	<select id="selectImageList" resultMap="boardImage_rm">
		SELECT * FROM BOARD_IMG
		WHERE BOARD_NO = #{boardNo}
		ORDER BY IMG_ORDER
	</select>
	
	<!-- 특정 게시글 댓글 조회(바뀔 예정) -->
	<select id="selectCommentList" resultMap="comment_rm">
		SELECT COMMENT_NO, COMMENT_CONTENT,
			TO_CHAR(C_CREATE_DATE, 'YYYY"년" MM"월" DD"일" HH24"시" MI"분" SS"초"') C_CREATE_DATE,
			BOARD_NO, MEMBER_NO, MEMBER_NICKNAME, PROFILE_IMG, PARENT_NO, COMMENT_DEL_FL
		FROM "COMMENT"
		JOIN MEMBER USING(MEMBER_NO)
		WHERE BOARD_NO = #{boardNo}
		ORDER BY COMMENT_NO
	</select>
	
	
	<!-- 좋아요 여부 확인 -->
	<select id="boardLikeCheck" resultType="_int">
		SELECT COUNT(*) FROM BOARD_LIKE
		WHERE BOARD_NO = #{boardNo}
		AND MEMBER_NO = #{memberNo}
	</select>
	
	<!-- 조회 수 증가 -->
	<update id="updateReadCount">
		UPDATE "BOARD" SET
		READ_COUNT = READ_COUNT + 1
		WHERE BOARD_NO = #{boardNo}
	</update>
	
	<!-- 좋아요 삽입 -->
	<insert id="insertBoardLike">
		INSERT INTO "BOARD_LIKE" VALUES (#{boardNo}, #{memberNo})
	</insert>
	
	<!-- 좋아요 삭제 -->
	<delete id="deleteBoardLike">
		DELETE FROM "BOARD_LIKE"
		WHERE BOARD_NO = #{boardNo}
		AND MEMBER_NO = #{memberNo}
	</delete>
	
	<!-- 좋아요 조회 -->
	<select id="countBoardLike" resultType="_int">
		SELECT COUNT(*) FROM "BOARD_LIKE" WHERE BOARD_NO = #{boardNo}
	</select>
	
	
	<!-- 게시글 삽입 -->
	<!-- 
		동적 SQL
		- 프로그램 수행 중 SQL을 변경하는 기능
		
		<selectKey> 태그 : INSERT/UPDATE 시 사용할 키(시퀀스)를
					조회해서 파라미터의 지정된 필드 대입
	
		useGeneratedKeys : DB 내부적으로 생성한키 (시퀀스) 를
					전달된 파라미터의 필드로 대입 가능 여부 지정
					
		order="BEFORE" : 메인 SQL이 수행되기 전에 selectKey가 수행되도록 지정
		
		keyProperty : selectKey 조회 결과를 저장할 파라미터의 필드
					Board의 boardNo 필드에다가 조회 결과를 저장하겠다.			
	
	 -->
	
	
	<insert id="boardInsert" parameterType="Board" useGeneratedKeys="true">
	
		<selectKey order="BEFORE" resultType="_int" keyProperty="boardNo">
			SELECT SEQ_BOARD_NO.NEXTVAL FROM DUAL
		</selectKey>
		
			INSERT INTO BOARD
			VALUES( #{boardNo},
					#{boardTitle},
					#{boardContent},
					DEFAULT, DEFAULT, DEFAULT, DEFAULT,
					#{memberNo},
					#{boardCode} )
	</insert>
	
	
	<!-- 
		동적 SQL 중 <foreach>
		- 특정 sql 구문을 반복할 때 사용
		- 반복되는 사이에 구분자를 추가할 수 있음.
	
	 -->
	 
	 <!-- 이미지 리스트(여러개)삽입 -->
	 <insert id="insertImageList" parameterType="list">
	 	INSERT INTO "BOARD_IMG"
	 	SELECT SEQ_IMG_NO.NEXTVAL, A.*
	 	FROM (
	 		
	 		<foreach collection="list" item="img" separator=" UNION ALL ">
	 			SELECT #{img.imagePath} IMG_PATH,
	 				#{img.imageReName} IMG_RENAME,
	 				#{img.imageOriginal} IMG_ORIGINAL,
	 				#{img.imageOrder} IMG_ORDER,
	 				#{img.boardNo} BOARD_NO
	 			FROM DUAL
	 		</foreach>
	 	) A
	 	
	 </insert>
	 
	 
	 <!-- 게시글 수정 -->
	 <update id="boardUpdate">
	 	UPDATE "BOARD" SET
	 	BOARD_TITLE = #{boardTitle},
	 	BOARD_CONTENT = #{boardContent},
	 	B_UPDATE_DATE = SYSDATE
	 	WHERE BOARD_CODE = #{boardCode}
	 	AND BOARD_NO = #{boardNo}
	 </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>
	
	
	
	
	 

</mapper>

ImageDeleteException.java

package edu.kh.project.board.model.exception;

public class ImageDeleteException extends RuntimeException{

	public ImageDeleteException() {
		super("이미지 삭제 실패");
	}
	
	public ImageDeleteException(String message) {
		super(message);
	}
	
}


게시글 수정 시, 수정된 내용으로 변경됨


게시글 삭제
스스로 해보기 !

<< Hint >>

boardDetail.js

BoardController2.java


0개의 댓글