Notice
Recent Posts
Recent Comments
Link
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
Archives
Today
Total
관리 메뉴

KSI일기장

0508spring 게시판(4) 글 작성, 수정 본문

Spring

0508spring 게시판(4) 글 작성, 수정

MyDiaryYo 2023. 5. 8. 16:01

board.js

// 상세조회, 게시글 작성 - 목록으로 버튼

// 즉시 실행 함수
(function(){
    const goToListBtn = document.getElementById("goToListBtn");

    if(goToListBtn != null){ // 목록으로 버튼이 화면에 있을 때만 이벤트 추가

        goToListBtn.addEventListener("click", function(){

            // location 객체(BOM)

            const pathname = location.pathname; // 주소상에서 요청 경로 반환
            //  /community/board/detail

            // 문자열.substring(시작,끝) : 시작 이상 끝 미만 인덱스 까지 문자열 자르기

            // 문자열.indexOf("검색 문자열", 시작 인덱스)
            // : 문자열에서 "검색 문자열"의 위치(인덱스)를 찾아서 반환
            //  단, 시작 인덱스 이후 부터 검색

            // 이동할 주소 저장
            let url = pathname.substring(0,  pathname.indexOf("/", 1)); // 
            //   /community

            url += "/board/list/"+boardCode+"?";  //   /board/list/1?cp=1


            // URL 내장 객체 : 주소 관련 정보를 나타내는 객체
            // location.href : 현재 페이지 주소 + 쿼리스트링
            // URL.searchParams : 쿼리 스트링만 별도 객체로 반환

            // http://localhost:8080/community/board/detail?no=249&cp=6&type=1&key=c&query=9
            const params = new URL(location.href).searchParams;

            let cp;

            if(params.get("cp") != null){ // 쿼리스트링에 cp가 있을 경우
                cp =  "cp=" + params.get("cp");     
            }else{
                cp = "cp=1";
            }

            // 조립
            //   /commy/board/list/1?cp=1
            url += cp;


            // 검색 key, query가 존재하는 경우 url에 추가
            if(params.get("key") != null){
                const key = "&key=" + params.get("key");
                const query = "&query=" + params.get("query");

                url += key + query; // url 뒤에 붙이기
            }


            // location.href = "주소";  -> 해당 주소로 이동
            location.href = url; 

        });
    }

})();



// 즉시 실행 함수
(function(){
    const thumbnail = document.getElementsByClassName("list-thumbnail");

    if(thumbnail.length > 0){ // 목록에 썸네일 이미지가 있을 경우에만 이벤트 추가
      
        const modal = document.querySelector('.modal');
        const modalImage = document.getElementById("modal-image");
        const modalClose = document.getElementById("modal-close");

        for(let th of thumbnail){
            th.addEventListener("click", function(){
                modalImage.setAttribute("src", th.getAttribute("src") );
               
                /* on/off 스위치 */
                // classList.toggle("클래스명") : 클래스가 없으면 추가(add) 
                //                                클래스가 있으면 제거(remove)
                
                modal.classList.toggle('show'); // add
            });
        }

        // X버튼
        modalClose.addEventListener("click", function(){
            
            modal.classList.toggle('hide'); // hide 클래스 추가

            setTimeout(function(){ // 0.45초 후 동작
                modal.classList.toggle('hide'); // hide 클래스 제거

                modal.classList.toggle('show'); // remove
            }, 450);
        });


    }

})();


// 즉시 실행 함수 : 성능up, 변수명 중복 X
(function(){
    const deleteBtn = document.getElementById("deleteBtn"); // 존재하지 않으면 null

    if(deleteBtn != null){ // 버튼이 화면에 존재할 때
        deleteBtn.addEventListener("click", function(){
            // 현재 : /board/detail/{boardCode}/{boardNo}
            // 목표 : /board/delete/{boardCode}/{boardNo}
      
            let url = contextPath + "/board/delete/" + boardCode + "/" +  boardNo;
            // 삭제 성공 -> 해당 게시판 목록 조회 1페이지로 리다이렉트
            // 삭제 실패 -> 요청 이전 페이지(referer)로 리다이렉트

            // UPDATE BOARD SET
            // BOARD_ST = 'Y'
            // WHERE BOARD_NO = #{boardNo}


            if( confirm("정말로 삭제 하시겠습니까?") ){
                location.href = url; // get방식으로 url에 요청
            }

        });
    }

})();


// 검색창에 이전 검색기록 반영하기
(function(){
    const select = document.getElementById("search-key");

    // const option = select.children;
    const option = document.querySelectorAll("#search-key > option");

    const input = document.getElementById("search-query");

    if(select != null){ // 검색창이 화면이 존재할 때에만 코드 적용

        // 현재 주소에서 쿼리스트링(파라미터) 얻어오기
        const params = new URL(location.href).searchParams;

        // 얻어온 파라미터 중 key, query만 변수에 저장
        const key = params.get("key");
        const query = params.get("query");

        // input에 query 값 대입
        input.value = query;

        // option을 반복 접근해서 value와 key가 같으면 selected 속성 추가
        for(let op of option){
            if(op.value == key ){
                op.selected = true;
            }
        }
    }

})();



// 검색 유효성 검사(검색어를 입력 했는지 확인)
function searchValidate(){

    const query = document.getElementById("search-query");

    if(query.value.trim().length == 0){ // 미작성
        query.value = ""; // 빈칸
        query.focus();

        return false;
    }

    return true;
}

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


<!DOCTYPE html>
<html lang="en">
<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>게시판</title>

    <link rel="stylesheet" href="${contextPath}/resources/css/main-style.css">
    <link rel="stylesheet" href="${contextPath}/resources/css/boardDetail-style.css">

    <link rel="stylesheet" href="${contextPath}/resources/css/reply-style.css">


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

        <section class="board-detail">  

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

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

                    <c:if test="${empty detail.profileImage}">
                        <!-- 프로필 이미지가 없는 경우 -->
                        <img src="${contextPath}/resources/images/user.png">
                    </c:if>

                    <c:if test="${ !empty detail.profileImage}">
                         <!-- 프로필 이미지가 있는 경우 -->
                        <img src="${contextPath}${detail.profileImage}">
                    </c:if>

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

                </div>

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

                    <c:if test="${ !empty detail.updateDate}">
                        <!-- updateDate가 존재하는 경우 -->
                        <p> <span>마지막 수정일</span>  ${detail.updateDate} </p>   
                    </c:if>

                    <p> <span>조회수</span>         ${detail.readCount} </p>                    
                </div>
            </div>

            <!-- 이미지가 있을 경우 -->
            <c:if test="${!empty detail.imageList}">
                
                <!-- 썸네일이 있을 경우 변수 생성 -->
                <c:if test="${detail.imageList[0].imageLevel == 0}">
                    <c:set var="thumbnail" value="${detail.imageList[0]}" />
                    <!-- page scope (페이지 어디서든 사용 가능) -->
                </c:if>


                <!-- 썸네일 영역(썸네일이 있을 경우) -->
                <c:if test="${!empty thumbnail}">
                    <h5>썸네일</h5>
                    <div class="img-box">
                        <div class="boardImg thumbnail">
                            <img src="${contextPath}${thumbnail.imageReName}">
                            <a href="${contextPath}${thumbnail.imageReName}" download="${thumbnail.imageOriginal}">다운로드</a>         
                        </div>
                    </div>
                </c:if>



                <c:if test="${empty thumbnail}"> <!-- 썸네일 X -->
                    <c:set var="start" value="0"/>
                </c:if>
                
                <c:if test="${!empty thumbnail}"> <!-- 썸네일 O -->
                    <c:set var="start" value="1"/>
                </c:if>


                <!-- 업로드 이미지가 있는 경우 -->
                <c:if test="${fn:length(detail.imageList) > start}">

                    <!-- 업로드 이미지 영역 -->
                    <h5>업로드 이미지</h5>
                    <div class="img-box">
                        <c:forEach var="i" begin="${start}" end="${fn:length(detail.imageList) -1 }">
                        
                            <div class="boardImg">
                                <img src="${contextPath}${detail.imageList[i].imageReName}">
                                <a href="${contextPath}${detail.imageList[i].imageReName}" download="${detail.imageList[i].imageOriginal}">다운로드</a>                
                            </div>

                        </c:forEach>
                    </div>

                </c:if>        

            </c:if>

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


            <!-- 버튼 영역-->
            <div class="board-btn-area">

                <c:if test="${loginMember.memberNo == detail.memberNo}">
                                                            <!-- detail?type=1&cp=3&no=100 -->
                                                            <!-- detail?no=1522&type=2 -->
                    <%-- cp가 없을 경우에 대한 처리 --%>
                    <c:if test="${empty param.cp}">
                        <!-- 파라미터에 cp가 없을 경우 1 -->
                        <c:set var="cp" value="1" />
                    </c:if>

                    <c:if test="${!empty param.cp}">
                        <!-- 파라미터에 cp가 있을 경우 param.cp -->
                        <c:set var="cp" value="${param.cp}" />
                    </c:if>


                    <button id="updateBtn" onclick="location.href='../../write/${boardCode}?mode=update&cp=${cp}&no=${detail.boardNo}'">수정</button>                       
                    <button id="deleteBtn">삭제</button>
                </c:if>

                <!-- onclick="history.back();"  뒤로가기 
                    history.go(숫자) : 양수(앞으로가기), 음수(뒤로가기)
                -->
                <button id="goToListBtn">목록으로</button>
            </div>
        </section>

        <!-- 댓글 -->
        <jsp:include page="/WEB-INF/views/board/reply.jsp"/>

    </main>

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


    <!-- jQuery 추가 -->
    <script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>

    <script src="${contextPath}/resources/js/board/board.js"></script>


    <script>
        // 댓글 관련 JS 코드에 필요한 값을 전역 변수로 선언

        // jsp 파일 : html, css, js, el, jstl 사용 가능
        // js  파일 : js

        // 코드 해석 순서  :   EL == JSTL > HTML > JS

        // ** JS 코드에서 EL/JSTL을 작성하게 된다면 반드시 ""를 양쪽에 추가 **

        // 최상위 주소
        const contextPath = "${contextPath}";
        
        // 게시글 번호
        const boardNo = "${detail.boardNo}"; // "500"

        // 로그인한 회원 번호
        const loginMemberNo = "${loginMember.memberNo}";
        // -> 로그인 O  : "10";
        // -> 로그인 X  : "";  (빈문자열)

       // 게시판 번호
		const boardCode = "${boardCode}";
        
    </script>

    <script src="${contextPath}/resources/js/board/reply.js"></script>



</body>
</html>

 

 

 

 

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

<!-- map에 저장된 값을 각각 변수에 저장 -->

<c:forEach var = "boardType" items="${boardTypeList}">

	<c:if test="${boardCode == boardType.boardCode}">
		<c:set var="boardName" value="${boardType.boardCode}" />
	</c:if>

</c:forEach>


<c:set var="pagination" value="${map.pagination}" />
<c:set var="boardList" value="${map.boardList}" />


<!DOCTYPE html>
<html lang="en">
<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="${contextPath}/resources/css/main-style.css">
    <link rel="stylesheet" href="${contextPath}/resources/css/boardList-style.css">

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

        <%-- 검색을 진행한 경우 key, query를 쿼리스트링 형태로 저장한 변수 생성 --%>
        <c:if test="${!empty param.key}">
            <c:set var="sURL" value="&key=${param.key}&query=${param.query}" />
        </c:if>

        <section class="board-list">

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

            <c:if test="${!empty param.key}">
                <h3 style="margin-left:30px;"> "${param.query}" 검색 결과  </h3>
            </c:if>



            <div class="list-wrapper">
                <table class="list-table">
                    
                    <thead>
                        <tr>
                            <th>글번호</th>
                            <th>제목</th>
                            <th>작성자</th>
                            <th>작성일</th>
                            <th>조회수</th>
                        </tr>
                    </thead>

                    <tbody>

                        <c:choose>
                            <c:when test="${empty boardList}">
                                <!-- 게시글 목록 조회 결과가 비어있다면 -->
                                <tr>
                                    <th colspan="5">게시글이 존재하지 않습니다.</th>
                                </tr>
                            </c:when>

                            <c:otherwise>
                                <!-- 게시글 목록 조회 결과가 비어있지 않다면 -->

                                <!-- 향상된 for문처럼 사용 -->
                                <c:forEach var="board" items="${boardList}">
                                    <tr>
                                        <td>${board.boardNo}</td>
                                        <td> 
                                            <c:if test="${!empty board.thumbnail}">
                                                <img class="list-thumbnail" src="${contextPath}${board.thumbnail}">
                                            </c:if>  

                                            <a href="../detail/${boardCode}/${board.boardNo}?cp=${pagination.currentPage}${sURL}">${board.boardTitle}</a>                           
                                      		<%-- "detail?no=${board.boardNo}&cp=${pagination.currentPage}&type=${param.type}${sURL}" --%>
                                      		<%--  
                                      		현제페이지(게시판 첫페이지) 주소 : /board/list/1?cp=1
                                      		상세 조회 주소 : /board/detail/1/300?cp=
                                      		 --%>
                                      		<%-- ../detail/${boardCode}/${board.boardNo} == board/detail/{boardCode}/{boardNo} --%>
                                      
                                        </td>
                                        <td>${board.memberNickname}</td>
                                        <td>${board.createDate}</td>
                                        <td>${board.readCount}</td>
                                    </tr>
                                </c:forEach>

                            </c:otherwise>
                        </c:choose>
                    </tbody>
                </table>
            </div>


            <div class="btn-area">

                <c:if test="${!empty loginMember}">
                    <!-- /community/board/write -->
                    <%-- <button id="insertBtn" onclick="location.href='write?mode=insert&type=${param.type}&cp=${param.cp}'">글쓰기</button>  --%>       
                     <button id="insertBtn" onclick="location.href='../write/${boardCode}?mode=insert&cp=${pagination.currentPage}'">글쓰기</button>            
                </c:if>

            </div>

            
            <div class="pagination-area">

                <!-- 페이지네이션 a태그에 사용될 공통 주소를 저장한 변수 선언 -->
                <c:set var="url" value="${boardCode}?cp="/>


                <ul class="pagination">
                    <!-- 첫 페이지로 이동 -->
                    <li><a href="${url}1${sURL}">&lt;&lt;</a></li>

                    <!-- 이전 목록 마지막 번호로 이동 -->
                    <li><a href="${url}${pagination.prevPage}${sURL}">&lt;</a></li>

                    <!-- 범위가 정해진 일반 for문 사용 -->
                    <c:forEach var="i" begin="${pagination.startPage}" end="${pagination.endPage}" step="1">

                        <c:choose>
                            <c:when test="${i == pagination.currentPage}">
                                <li><a class="current">${i}</a></li>
                            </c:when>

                            <c:otherwise>
                                <li><a href="${url}${i}${sURL}">${i}</a></li>        
                            </c:otherwise>
                        </c:choose>

                    </c:forEach>
                    
                    <!-- 다음 목록 시작 번호로 이동 -->
                    <li><a href="${url}${pagination.nextPage}${sURL}">&gt;</a></li>

                    <!-- 끝 페이지로 이동 -->
                    <li><a href="${url}${pagination.maxPage}${sURL}">&gt;&gt;</a></li>

                </ul>
            </div>

            <!-- /board/list?type=1&cp=3 -->

            <!-- /board/list?type=1&cp=10 &key=t&query=안녕 -->

            <form action="list" method="get" id="boardSearch" onsubmit="return searchValidate()">
                <input type="hidden" name="type" value="${param.type}">

                <select name="key" id="search-key">
                    <option value="t">제목</option>
                   <option value="c">내용</option>
                    <option value="tc">제목+내용</option>
                    <option value="w">작성자</option>
                </select>

                <input type="text" name="query"  id="search-query" placeholder="검색어를 입력해주세요.">

                <button>검색</button>
            </form>

        </section>
    </main>
    
    <div class="modal">
        <span id="modal-close">&times;</span>
        <img id="modal-image" src="${contextPath}/resources/images/user.png">
    </div>


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

    <script src="${contextPath}/resources/js/board/board.js"></script>



</body>
</html>

 

BoardController

//게시글 작성,수정 화면 전환

  //<button id="insertBtn" onclick="location.href='../write/${boardCode}?mode=insert&cp=${pagination.currentPage}'">글쓰기</button>
 //<button id="updateBtn" onclick="location.href='../../write/${boardCode}?mode=update&cp=${cp}&no=${detail.boardNo}'">수정</button>
      //작성화면 전환	
        @GetMapping("/write/{boardCode}")	
		public String boardWriterForm(@PathVariable("boardCode") int boardCode,
									  String mode,// ==@RequestParam
									  Model model, //Model을 이용해 view단에 데이터를 넘겨줄 수 있다
									  @RequestParam(value="no", required=false, defaultValue="0") int boardNo) {
									//-> insert의 경우 파라미터에 no(게시물번호)가 없을 수 있어서

			if(mode.equals("update")) {	//수정화면 전환	
            			//mode가 update일 경우
				//BoardWriteForm.jsp에서 
				// <c:if test="${param.mode == 'update'}"> 
                        //<button id="updateBtn" onclick="location.href='../../write/${boardCode}?mode=update&cp=${cp}&no=${detail.boardNo}'">수정</button>
                        //mode=update

				//게시글 상세조회 서비스호출(boardNo)
				BoardDetail detail = service.selectBoardDetail(boardNo);
				//->개행문자가 <br>태그로 되있는 상태
				//->textarea출력 예정이기 때문에 \n으로 변경 필요
				//->Util 클래스에 있음 <br> -> \n
				//Util.newLineClear(detail.getBoardContent());
				detail.setBoardContent( Util.newLineClear(detail.getBoardContent()) );

				model.addAttribute("detail", detail);
                //Model을 이용해 view단에 데이터를 넘겨줄 수 있다

			}

			return "board/boardWriteForm";
			// WEB-INF/views/board/boardWriteFrom
		}


	//개행문자가 <br>로 되있는 상태 -> textarea 출력하려면 \n 변경해야한다
	//-> Util.newLineClear() 메서드 사용해서

	//게시글 작성 (삽입/수정)
    //<button id="insertBtn" onclick="location.href='write?mode=insert&type=${param.type}&cp=${param.cp}'">글쓰기</button> 
//<button id="updateBtn" onclick="location.href='write?mode=update&type=${param.type}&cp=${cp}&no=${detail.boardNo}'">수정</button>
// <form action="${boardCode}" enctype="multipart/form-data" method="POST" 
	// "/board/write/{boardCode}"
		@PostMapping("/write/{boardCode}")
		public String boardWrite(BoardDetail detail,	//boardTitle, boardContent, boardNo
								@RequestParam(value="images", required=false) List<MultipartFile> imageList,	//업로드 파일(이미지) 리스트
				 				@PathVariable("boardCode") int boardCode,
				 				String mode,
				 				@ModelAttribute("loginMember") Member loginMember,
				 				RedirectAttributes ra,
				 				HttpServletRequest req, //이미지 저장 경로 얻어오기 위해 쓰일 것
				 				@RequestParam(value="cp", required=false, defaultValue="1") int cp,
								@RequestParam(value="deleteList", required=false) String deleteList
								//boardWriteForm.jsp에서 name="deleteList
								) throws Exception{

			//1.로그인한 회원번호 얻어와 detail에 세팅
			detail.setMemberNo(loginMember.getMemberNo());

			//2.이미지 저장 경로 얻어오기(webPath, folderPath)
			String webPath = "/resources/images/board/";
			String folderPath = req.getSession().getServletContext().getRealPath(webPath); //실제 이미지파일 경로

			//3.삽입인지 수정인지
			if(mode.equals("insert")) {	//BoardWriteForm.jsp에서 
				// <c:if test="${param.mode == 'insert'}"> , 
				//mode == intsert일 경우 삽입 
                //<button id="insertBtn" onclick="location.href='../write/${boardCode}?mode=insert&cp=${pagination.currentPage}'">글쓰기</button>
            	//에서 mode=insert 
            
				//1)게시글 부분 삽입(이미지 빼고 제목,내용,회원번호,게시판코드)
				//->삽입끝난 후 게시글번호(boardNo) 반환 (이유: 게시글삽입이 끝나면 게시글 상제조회로 리다이렉트할거기 때문에)


				//2)게시글에 포함된 이미지 정보 삽입 (이미지0~5개 (게시글번호 필요) )
				//->실제 파일로 변환해서 서버에 저장(transferTo())

				//3)위 두번의 insert중 한번이라도 실패하면 전체 rollback(트랜잭션처리)


				int boardNo = service.insertBoard(detail, imageList, webPath, folderPath);

				String path = null;
				String message = null;

				if(boardNo>0) {
					//
					//현재주소 /board/write/1
					// ->게시글 등록 후 게시글 상세조회 페이지  /board/detail/1/1500

					path = "../detail/" + boardCode + "/" + boardNo;
					message = "게시글이 등록되었습니다";
				}else {
					path = req.getHeader("referer");	//referer ( 이전페이지 url)
					//실패시 이전페이지로 돌아간다
					message = "게시글 삽입 실패";
				}

				ra.addFlashAttribute("message",message);

				return "redirect:" + path;


			}else {	//	//BoardWriteForm.jsp에서
				// <c:if test="${param.mode == 'update'}"> 
				//mode가 update인 경우 수정

				//게시글 수정 서비스 호출
				//게시글 번호를 알고있기 때문에 수정결과만 반환 받으면 된다
				int result = service.updateBoard(detail, imageList, webPath, folderPath, deleteList);
				String path = null;
				String message = null;

				if(result>0) {
					//현재주소 /board/write/1
					// ->게시글 등록 후 게시글 상세조회 페이지  /board/detail/1/1500
					//											/board/detail/{boardCode}/{boardNo}?cp=10
					path = "../detail/" + boardCode + "/" + detail.getBoardNo() + "?cp=" + cp;

					message = "게시글이 등록되었습니다";
				}else {
					path = req.getHeader("referer");	//referer ( 이전페이지 url)
					//실패시 이전페이지로 돌아간다
					message = "게시글 수정 실패";
				}

				ra.addFlashAttribute("message",message);
				return "redirect:" + path;
			}

		}

 

BoardService

/**게시글 수정 서비스
	 * @param detail
	 * @param imageList
	 * @param webPath
	 * @param folderPath
	 * @param deleteList
	 * @return result
	 * @throws IOException
	 * @throws Exception
	 */
	int updateBoard(BoardDetail detail, List<MultipartFile> imageList, String webPath, String folderPath,
			String deleteList) throws Exception, IOException;

 

BoardServiceImpl

//게시글삽입 + 이미지 삽입 서비스

	//spring에서 트랜잭션 처리하는 방법

	//AOP(관점지향프로그래밍)를 이용해 (DAO에서 Service보낼때) or (Service코드 수행 시점)에
	//예외가 발생하면 rollback을 수행

	//방법1. <tx:advice> XML을 이용한 방법 -> 패턴을 지정해서 일치하는 메서드 호출 시 자동으로 트랜잭션 제어

	//방법2. @Transactional 선언적 트랜잭션 처리 방법 -> RuntimeException 예외처리를 기본값으로 갖는다
	//rollbackFor :rollback을 수행하기 위한 예외의 종류를 작성

	//RuntimeException (Unchecked Exception)
	//checked Exception :예외처리가 필수( transferTo() ) -> SQL관련 예외, 파일업로드 관련 예외
	//Unchecked Exception :예외처리가 선택적 ( ex. int a = 10/0; )
	@Transactional(rollbackFor = {Exception.class})
	@Override
	public int insertBoard(BoardDetail detail,
							List<MultipartFile> imageList,
							String webPath, String folderPath) throws IOException {

		//1.게시글 삽입

		//1)XSS방지처리 + 개행문자 처리 (Util에 있음)
		detail.setBoardTitle(Util.XSSHandling(detail.getBoardTitle()));
		detail.setBoardContent(Util.XSSHandling(detail.getBoardContent()));
		//->XSS방지 처리

		detail.setBoardContent(Util.newLineHandling(detail.getBoardContent()));
		//-> 개행문자처리 <br> -> \n 으로 변환 (Util에 있음)

		//2)게시글삽입 DAO호출 후 게시글 번호 반환받기
			//1.서비스 결과 반환 후 컨트롤러에서 상세조회로 리다이렉트 하기 위해
			//2.동일한 시간에 삽입이 2회이상(거의동시에) 진행되는 경우 시퀀스 번호가 의도와 달리 여러번 증가해
			// 이후에 작성된 이미지 삽입 코드에 영향 미치는걸 방지하기 위해
		int boardNo = dao.insertBoard(detail);

		List<BoardImage> boardImageList = new ArrayList<>();
		List<String> reNameList = new ArrayList<>();

		if(boardNo>0) {
			//이미지 삽입

			//imageList :실제파일이 담겨있는 리스트
			//boardImageList :DB에 삽입할 이미지정보만 담겨있는 리스트
			//reNameList :변경된 파일명 담겨있는 리스트


			//imageList에 담겨있는 파일정보 중 실제업로드된 파일만 분류하는 작업
			//리스트에서 실제파일이 들어가서 업로드될 파일만 분류

			for(int i = 0; i<imageList.size(); i++) {	//i번째 요소에 업로드된 이미지가 있을경우
				//변경된 파일명 저장
				//파일명 ex. 20220222_10
				String reName = Util.fileRename(imageList.get(i).getOriginalFilename());
				reNameList.add(reName);

				//BoardList객체를 생성해 값 세팅 후 boardImageList에 추가
				BoardImage img = new BoardImage();
				img.setBoardNo(boardNo);	//게시글 번호
				img.setImageLevel(i);	//이미지 순서(파일레벨)
				img.setImageOriginal(imageList.get(i).getOriginalFilename()); 	//원본파일명
				img.setImageReName(webPath + reName); 	//웹 접근경로 + 변경된 파일명

				boardImageList.add(img);
			}//end of for

		}

		//분류 작업 종료 후 boardImageList가 비어있지 않은 경우 ( == 파일 업로드가 된 경우)
		if(!boardImageList.isEmpty()) {
			int result = dao.insertBoardImageList(boardImageList);

			//result == 삽입 성공한 행 개수
			if(result == boardImageList.size() ) {	//삽입된 행의 개수와 업로드 이미지 수가 같을 경우

				//서버에 이미지 저장
				for(int i = 0; i<boardImageList.size(); i++) {
					int index = boardImageList.get(i).getImageLevel();

					imageList.get(index).transferTo( new File(folderPath + reNameList.get(i) ) );
				}

			}else {	//이미지 삽입 실패시

				//강제로 예외 발생시켜 rollback을 수행하게 한다
				//-> 사용자 정의 예외
				throw new InsertFailException();

			}

		}

		return boardNo;
	}


	//게시글 수정
	@Transactional(rollbackFor = {Exception.class} )	//모든종류예외(Exception)발생시 rollback한다는 의미
	@Override
	public int updateBoard(BoardDetail detail, List<MultipartFile> imageList, String webPath, String folderPath,
			String deleteList) throws Exception, IOException {

		//1)XSS방지처리 + 개행문자 처리 (Util에 있음)
			detail.setBoardTitle(Util.XSSHandling(detail.getBoardTitle()));
			detail.setBoardContent(Util.XSSHandling(detail.getBoardContent()));
			//->XSS방지 처리

			detail.setBoardContent(Util.newLineHandling(detail.getBoardContent()));
			//-> 개행문자처리 <br> -> \n 으로 변환 (Util에 있음)

		//2)게시글만(제목, 내용, 마지막수정일(sysdate) 수정하는 DAO호출
			int result = dao.updateBoard(detail);

			if(result>0) {
				//3)업로드된 이미지만 분류
				List<BoardImage> boardImageList = new ArrayList<>();
				List<String> reNameList = new ArrayList<>();

					//이미지 삽입

					//imageList :실제파일이 담겨있는 리스트
					//boardImageList :DB에 삽입할 이미지정보만 담겨있는 리스트
					//reNameList :변경된 파일명 담겨있는 리스트

					//imageList에 담겨있는 파일정보 중 실제업로드된 파일만 분류하는 작업
					//리스트에서 실제파일이 들어가서 업로드될 파일만 분류

					for(int i = 0; i<imageList.size(); i++) {	//i번째 요소에 업로드된 이미지가 있을경우


						if(imageList.get(i).getSize() > 0) {

						//변경된 파일명 저장
						//파일명 ex. 20220222_10
						String reName = Util.fileRename(imageList.get(i).getOriginalFilename());
						reNameList.add(reName);

						//BoardList객체를 생성해 값 세팅 후 boardImageList에 추가
						BoardImage img = new BoardImage();
						img.setBoardNo(detail.getBoardNo());	//게시글 번호
						img.setImageLevel(i);	//이미지 순서(파일레벨)
						img.setImageOriginal(imageList.get(i).getOriginalFilename()); 	//원본파일명
						img.setImageReName(webPath + reName); 	//웹 접근경로 + 변경된 파일명

						boardImageList.add(img);
					}//end of if

			}//end of for

			//deleteList를 이용해서 삭제된, 이미 delete
				if(!deleteList.equals("")) {
					Map<String, Object> map = new HashMap<>();

					map.put("boardNo", detail.getBoardNo());
					map.put("deleteList", deleteList);

					result = dao.deleteBoardImage(map);
				}

				if(result>0) {

					//5)boardImageList를 순차 접근하면서 하나씩 update
					for(BoardImage img : boardImageList) {
						result = dao.updateBoardImage(img);	//변경할명, 원본명, 게시글번호, 레벨

						//결과1	-> 수정 완료(기존이미지 존재)
						//->update

						//결과0 -> 수정 실패(기존이미지 존재X)
						//->insert


						//-> image가 기존에 4개 자리중 2개있는 경우 1개추가하면 insert
						//-> image가 기존에 4개 자리중 4개있는 경우 1개바꾸면 update

						//6)update를 실패하면 insert
						if(result==0) {
							result = dao.insertBoardImage(img);
							//->값을 하나씩 대입해서 삽입하는 경우 결과가 0이 나올 수는 없다
							//단, 예외는 발생할 수 있다(제약조건위배 예외, sql문법 예외 등)
						}

					}//end of for

					//업로드된
					if(!boardImageList.isEmpty() && result != 0) {

						//서버에 이미지 저장
						for(int i = 0; i<boardImageList.size(); i++) {
							int index = boardImageList.get(i).getImageLevel();

							imageList.get(index).transferTo( new File(folderPath + reNameList.get(i) ) );
						}

					}


				}//end of if


			}
			return result;

	}

 

BoardDAO

	/**게시글 삽입 DAO
	 * @param detail
	 * @return
	 */
	public int insertBoard(BoardDetail detail) {

		int result = sqlSession.insert("boardMapper.insertBoard", detail);	//0 or 1

		if(result>0) {
			result = detail.getBoardNo();
		}

		return result;
	}


	/**게시글 이미지 삽입(리스트) DAO
	 * @param boardImageList
	 * @return result
	 */
	public int insertBoardImageList(List<BoardImage> boardImageList) {

		return sqlSession.insert("boardMapper.insertBoardImageList", boardImageList);
	}


	/**게시글 수정 DAO
	 * @param detail
	 * @return
	 */
	public int updateBoard(BoardDetail detail) {

		return sqlSession.update("boardMapper.updateBoard", detail);
	}


	/**게시글 이미지 삭제
	 * @param map
	 * @return
	 */
	public int deleteBoardImage(Map<String, Object> map) {

		return sqlSession.delete("boardMapper.deleteBoardImage", map);
	}


	/**게시글 이미지 1개 수정
	 * @param img
	 * @return
	 */
	public int updateBoardImage(BoardImage img) {

		return sqlSession.update("boardMapper.updateBoardImage", img);
	}


	/**게시글 이미지 1개 삽입
	 * @param img
	 * @return
	 */
	public int insertBoardImage(BoardImage img) {

		return sqlSession.insert("boardMapper.insertBoardImage", img);
	}

board-mapper.xml

<!-- 게시글 삽입 -->
	<!-- 
		 useGeneratedKeys : 기본값 false,
							DB내부적으로 생성한 키(ex.시퀀스)를 사용하겠다는 의미 
		 selectKey  :동적 SQL중 하나로, 
					 INSERT or UPDATE에 사용된 키(ex.시퀀스)를 원하는 변수/필드에 담아 반환하는 태그 
					 
		 keyProperty :key를 담을 변수/필드를 지정하는 속성
		 
		 order : INSERT/UPDATE에 작성된 메인 SQL이 수행되기전(BEFORE) 또는 후(AFTER)에
		 		 <selcetKey> 내부 SQL이 수행되도록 순서를 지정하는 속성
		-->
	<insert id="insertBoard" parameterType="detail" useGeneratedKeys="true">
		<selectKey keyProperty="boardNo" resultType="_int" order="BEFORE">
				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구문을 반복 사용할 때 사용
		 :반복되는 사이에 구분자(separator) 추가 가능
		 
		 collection: 반복할 객체의 타입 작성(list, set, map..)
		 item :collection에서 순차적으로 꺼낸 하나의 요소를 저장하는 변수
		 index :현재 반복 접근중인 인덱스(0,1,2,3,...)
		 
		 open :반복 전에 출력할 sql
		 close :반복 종료 후에 출력할 sql
		 
		 separator :반복 사이에 들어갈 구분자 -->
	
	<insert id="insertBoardImageList" parameterType="list">
	
		INSERT INTO BOARD_IMG 
		SELECT SEQ_IMG_NO.NEXTVAL IMG_NO,  A.* FROM(
		
			<foreach collection="list"  item="img" separator="UNION ALL" >
			    SELECT  #{img.imageReName}     IMG_RENAME, 
			    		#{img.imageOriginal}   IMG_ORIGINAL,
			        	#{img.imageLevel}      IMG_LEVEL, 
			        	#{img.boardNo}         BOARD_NO
			    FROM DUAL
			</foreach>
		) A
	</insert>
	
	<!-- 
	위 foreach 반복문을 풀면
    ->
	INSERT INTO BOARD_IMG 
		SELECT SEQ_IMG_NO.NEXTVAL IMG_NO,  A.* FROM(
		
			    SELECT  #{img.imageReName}     IMG_RENAME, 
			    		#{img.imageOriginal}   IMG_ORIGINAL,
			        	#{img.imageLevel}      IMG_LEVEL, 
			        	#{img.boardNo}         BOARD_NO
			    FROM DUAL
			 
			 UNION ALL
			 
			  SELECT  #{img.imageReName}     IMG_RENAME, 
			    		#{img.imageOriginal}   IMG_ORIGINAL,
			        	#{img.imageLevel}      IMG_LEVEL, 
			        	#{img.boardNo}         BOARD_NO
			    FROM DUAL
			 
			 UNION ALL
		) A
		......
		
	
	 -->
	
	
	<!-- 게시글 수정 -->
	<update id="updateBoard">
		UPDATE BOARD SET
		BOARD_TITLE = #{boardTitle},
		BOARD_CONTENT = #{boardContent},
		UPDATE_DT = SYSDATE
		WHERE BOARD_NO = #{boardNo}
	</update>
	
	
	<!-- 게시글 이미지 삭제 -->
	<!-- ***주의*** IN구문 작성시 ''가 없도록 $ 사용 -->
	<delete id="deleteBoardImage">
		DELETE FROM BOARD_IMG
		WHERE BOARD_NO = ${boardNo}
		AND IMG_LEVER IN ( ${deleteList} )
	</delete>
	

	
	<!-- 게시글 이미지 1개 수정 -->
	<update id="updateBoardImage">
		UPDATE BOARD_IMG SET
		IMG_RENAME = #{imageReName},
		IMG_ORIGINAL = #{imageOriginal}
		
		WHERE BOARD_NO = #{boardNo}
		AND IMG_LEVEL = #{imageLevel}
	</update>
	
	
	<!-- 게시글 이미지 1개 삽입 -->
	<insert id="insertBoardImage">
		INSERT INTO BOARD_IMG VALUES(
			SEQ_IMG_NO.NEXTVAL, 
			#{imageReName}, 
			#{imageOriginal},
			#{imageLevel}, 
			#{boardNo})
	</insert>