Deeb
디비의 DB
Deeb
전체 방문자
오늘
어제
  • 분류 전체보기 (243)
    • Frontend (63)
      • HTML & CSS (27)
      • JavaScript (17)
      • jQuery (8)
      • React (6)
    • Backend (98)
      • Java (19)
      • JDBC (2)
      • Servlet & JSP (13)
      • Spring (17)
      • Project (0)
      • 개발 공부 (11)
      • 문제 풀이 (8)
      • Algorithm (1)
      • DataBase (0)
      • Oracle (18)
      • Error (8)
    • Knou (1)
    • Review (14)
    • TIL (33)
    • 삽질기록 (8)
    • deebtionary (5)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 한빛미디어
  • 배열
  • GIT
  • CLASS
  • Java
  • 공부
  • 삭제
  • 서평단
  • 기초
  • 책
  • 에러
  • 다형성
  • alter
  • 리액트
  • DB
  • 방통대
  • 자바
  • 함수
  • DBMS
  • 정의
  • css
  • For
  • 방송대
  • 후기
  • 클래스
  • HTML
  • 추천
  • js
  • 2학기
  • 정처기

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
Deeb

디비의 DB

[Spring] 게시글 글 수정
Backend/Spring

[Spring] 게시글 글 수정

2022. 1. 19. 14:48

수정화면 전환 

BoardController

// 게시글 수정 화면 전환
@RequestMapping(value = "updateForm", method = RequestMethod.POST)
public String updateForm(int boardNo, Model model ) {

    // 1. 카테고리 목록 조회
    List<Category> category = service.selectCategory(); // 앞서 사용한 서비스를 재요청


    // 2. 게시글 상세 조회
    Board board = service.selectBoard(boardNo);

    model.addAttribute("category", category);
    model.addAttribute("board", board);

    return "board/boardUpdate";
}

BoardService

Board selectBoard(int boardNo);

BoardServiceImpl

// 수정화면 전환용 게시글 상세 조회
@Override
public Board selectBoard(int boardNo) {
    Board board = dao.selectBoard(boardNo);

    // <br> -> /r/n 으로 변경
    board.setBoardContent(Util.changeNewLine2(board.getBoardContent()));
    return board;
}

BoardDAO

/** 게시글 상세 조회
 * @param boardNo
 * @return board
 */
public Board selectBoard(int boardNo) {
    // 게시글 정보 + 해당 게시글 이미지 정보가 다 담겨있다
    return sqlSession.selectOne("boardMapper.selectBoard", boardNo);
}

board-mapper

<!-- 게시글상세조회 -->
	<select id="selectBoard" resultMap="board_rm">
		SELECT BOARD_NO, BOARD_TITLE, BOARD_CONTENT,
		    MEMBER_NO, MEMBER_NM,
		    TO_CHAR(CREATE_DT, 'YYYY"년" MM"월" DD"일" HH24"시" MI"분" SS"초"' ) CREATE_DT,
		    TO_CHAR(MODIFY_DT, 'YYYY"년" MM"월" DD"일" HH24"시" MI"분" SS"초"' ) MODIFY_DT,
		    CATEGORY_CD, CATEGORY_NM,
		    READ_COUNT, STATUS_NM
		FROM BOARD B
		JOIN MEMBER USING(MEMBER_NO)
		JOIN STATUS S ON(B.STATUS_CD = S.STATUS_CD)
		JOIN CATEGORY USING(CATEGORY_CD)
		WHERE BOARD_NO = #{boardNo}
		AND STATUS_CD != 4
	</select>

boardUpdate.jsp

141 줄 : 지웠다는 정보를 가지고 갈 태그

value를 하나의 스트링으로 전달하는데 SQL에서 받은 값의 레벨의 이미지가 다 지워지게 수행할 것이다 

 

DELETE FROM BOARD_IMG 

WHERE BOARD_N0 = 500

AND IMG_LEVEL IN ()

 

 

1. 스크립트 작성 BOARD.js

const deleteImages = [];

1) x버튼이 눌러져 삭제된 이미지 레벨을 저장할 배열 선언

- 배열로 받는 이유: 배열을 input 태그 value로 추가하면 '요소1, 요소2, 요소3, ...' 형태의 문자열로 변환된다

 

배열데이터를 인풋태그에 추가해서 value형태로 들어가는데 그 값은 문자열 '0,1,2'로 된다

 

 

2. x버튼 

// deleteImages 배열에 삭제된 이미지의 레벨을 추가 (인덱스번호는 위에서 받아져있음)

// 이미지에 있는 x버튼을 눌렀을 때의 동작
$(".deleteImg").on("click", function(e){
	// 매개변수 e : 이벤트 발생 객체(이벤트에 관련된 모든 정보를 담고 있다)

	e.stopPropagation(); 
	// stopPropagation():  이벤트 버블링(감싸고 있는 요소에 이벤트가 전파되는것을 막아준다) 멈춤게 하는것

	$(this). prev().removeAttr("src"); // 미리보기 이미지 삭제

	// 클릭된 x버튼의 인덱스와 같은 인덱스에 위치한 input type="file"요소의 value를 초기화
	const index = $(this).index(".deleteImg");
	$("input[name=images]").eq(index).val(""); 

	// deleteImages 배열에 같은 이미지 레벨(인덱스번호)가 없으면
	if(deleteImages.indexOf(index) == -1){
		// deleteImages 배열에 삭제된 이미지의 레벨을 추가 (인덱스번호는 위에서 받아져있음)
		deleteImages.push(index);
	}
});

 

2) 이미지 삭제를 DB에서 처리 

-- 게시글 수정시 이미지 삭제

DELETE FORM BOARD_IMG
WHERE BOARD_NO = 509
AND IMG_LEVEL IN (0,2,1,2,2);

레벨에 같은 이미지가 몇 번이고 들어와도 계속 삭제 되게해야한다. 

이미지 판별하는 코드 추가하는 응용도 가능

 

3) 다 수정한 뒤에 수정버튼 클릭시 유효성 검사가 진행되는데 배열에는 추가했으니 화면에도 추가하기

// 유효성 검사 
function boardValidate() {
	if ($("#boardTitle").val().trim().length == 0) {
		alert("제목을 입력해 주세요.");
		$("#title").focus();
		return false;
	}
	if ($("#boardContent").val().trim().length == 0) {
		alert("내용을 입력해 주세요.");
		$("#content").focus();
		return false;
	}

	// 유효성 검사 후 문제가 없다면
	document.querySelector("input[name=deleteImages]").value = deleteImages; // js
    // $("input[name=deleteImages]").val(deleteImages); // jquery
	// document.getElementsByName("deleteImages")[0].value = deleteImages; // js
}

- 유효성 검사 후 문제가 없다면
input[name=deleteImages] 요소의 value 값으로 deleteImages 배열을 추가 

 

3-test) 홈페이지에서 이미지를 삭제하면 삭제한 이미지 인덱스 번호가 나오는데 

deleteImages 배열에서 해당 인덱스인 0번의 인덱스 번호를 지운다

 

 4) deleteImages 배열에 num과 같은 번호가 존재하면 배열 내 지정 인덱스부터 지정된 개수만큼 요소 삭제

// 각각의 영역에 파일을 첨부 했을 경우 미리 보기가 가능하도록 하는 함수
function loadImg(input, num) { // 여기서의 num = index
	// 매개변수 value == 클릭된 input 요소

	// 미리보기 만들어 주는 부분
	// 파일이 선택된 경우 true
	if (input.files && input.files[0]) {
		// jsp에서 onchange에서 지정한 인덱스 번호가 여기서 Key가 된다
		// javascript는 일일이 복제하는게 필요하지만 jquery는 객체를 똑같이 복제하는 clone()이 있다
		fileClone[num] = $(input).clone(); // 백업 객체에 복제본 추가 

		// deleteImages 배열에 num과 같은 번호가 존재하는지 확인
		if(deleteImages.indexOf(num) != -1){ // 존재하는 경우에만 동작

			// 배열.splice(시작 인덱스, 제거할 인덱스 수) : 배열 내 지정 인덱스부터 지정된 개수만큼 요소 삭제
			deleteImages.splice( deleteImages.indexOf(num), 1);
		}

		var reader = new FileReader();
		reader.readAsDataURL(input.files[0]);
		reader.onload = function(e) {
			$(".boardImg").eq(num).children("img").attr("src", e.target.result);
		}

	} else {
		console.log("취소 클릭");
		
		$(input).before(fileClone[num].clone());
		//그로인해 생긴 문제 해결법  -> 원본 복제본의 복제본을 만들어 삽입한다.

		$(input).remove(); // 원본 삭제를 해  복제본만 남겨둔다
	}
}
$(input).before(fileClone[num]);

- input : 파일 태그 자체
- before : 삽입하면 끝이니 after도 상관없다
- 취소가 실행된 input태그 앞에 백업해둔 복제본을 추가한다. ==> 원본, 복제본 두개 만들어진다 


2. BoardController

 

메서드 생성 - 필요한 정보 (매개변수) : 게시글 정보 , cp, deleteImages, images, ra, session

@RequestMapping(value = "update", method = RequestMethod.POST)
	   public String updateBoard(Board board,
			   @ModelAttribute("loginMember") Member loginMember, 
			   @RequestParam(value = "cp", required = false, defaultValue = "1") int cp,
			   @RequestParam("deleteImages") String deleteImages, 
			   @RequestParam("images") List<MultipartFile> images,
			   RedirectAttributes ra, HttpSession session) {

		// 1) 웹 접근 경로(webPath), 서버 저장 경로(serverPath)
		String webPath = "/resources/images/board/"; //(db에 저장되는 경로)
		String serverPath = session.getServletContext().getRealPath(webPath);
		
		// 2) 게시글 수정 Service 호출
		int result = service.updateBoard(board,images,webPath,serverPath, deleteImages);
		
		// 수행한 결과에 따라 보낼 주소 redirect
		String path = null;
		
		if(result > 0) {
			Util.swalSetMessage("게시글 수정 성공", null, "success", ra);
			path = "view/" + board.getBoardNo() + "?cp=" + cp; // 왜 보드 넘버를 쿼리스트링으로 사용했지,,
			
		} else {
			Util.swalSetMessage("게시글 수정 실패", null, "error", ra);
			path = "updateForm";
		}
		
		return "redirect:" + path;
	}


매개변수 

Board board :게시글에서 넘겨받은 제목 내용등이 담긴 커맨드 객체

@RequestParam(value = "images", required = false) List<MultipartFile> images  : 이미지값, 필수는 아니다. List 형태로받는다 (이미지 파일)

RedirectAttributes ra : redirect 객체 전달

HttpSession session : 파일 저장 경로

System.out.println("deleteImages" +deleteImages);  출력으로 deleteImages의 값이 넘어오지 않을 때를 판별
deleteImages 가 비어있을 경우 == "" (빈 문자열)
deleteImages 가 비어있지 않을 경우 == "0,2" 처럼 문자열
=> null이 아닌 빈 문자열 ""

 

jsp에서 input태그의 값을 받아오는데 boardNo는 이미 매개변수에 있는 커맨드 객체인 board에 있으니 cp, deleteImages만 받아온다


BoardService

/** 게시글 수정
 * @param board
 * @param images
 * @param webPath
 * @param serverPath
 * @param deleteImages
 * @return result
 */
int updateBoard(Board board, List<MultipartFile> images, String webPath, String serverPath, String deleteImages);

BoardServiceImpl

1) 게시글 제목/ 내용  XSS 처리

1-1) 개행문자 처리

2) 게시글 부분 수정 진행 - dao 호출

3) 기존에 있었지만 삭제된 이미지 DELETE 처리 진행 

4) images에 담겨있는 파일 정보 중 업로드 된 파일 정보를 imgList에 옮겨 담기

5) imgList가 비어있지 않은 경우 -> imgList에 비어있는 내용을 update 또는 insert

6) 전달 받은 images 중 업로드된 파일이 있는 부분을 실제 파일로 저장 ( 전달 받은 images 는 imgList에 저장되어있다)

// 게시글 수정
@Transactional
@Override
public int updateBoard(Board board, List<MultipartFile> images, 
	String webPath, String serverPath, String deleteImages) {

    // 1) 게시글 제목/ 내용 XSS 처리
    board.setBoardTitle(Util.XSS(board.getBoardTitle()));
    board.setBoardContent(Util.XSS(board.getBoardContent()));
    // 1-1) 개행문자 처리
    board.setBoardContent(Util.changeNewLine(board.getBoardContent()));

    // 2) 게시글 부분 수정 진행 - dao 호출
    int result = dao.updateBoard(board);

    // 3) 기존에 있었지만 삭제된 이미지 DELETE 처리 진행 
    if(result > 0) {
        // 마이바티스는 SQL 수행 시 파라미터를 1개만 받을 수 있다.
        // 전달할 파라미터가 다수인 경우 Map과 같은 컬렉션 객체를 이용하면 된다.

        if(!deleteImages.equals("")) { // 삭제할 이미지가 있을 경우 
            Map<String, Object> map = new HashMap<String, Object>();
            map.put("boardNo", board.getBoardNo()); // boardNo는 int -> autoBoxing ->Integer -> Object
            map.put("deleteImages", deleteImages); // String -> Object

            result = dao.deleteImages(map);
        }
    }

    // 4) images에 담겨있는 파일 정보 중 업로드 된 파일 정보를 imgList에 옮겨 담기
    if(result > 0){

        List<BoardImage> imgList = new ArrayList<BoardImage>();

        for(int i =0; i<images.size(); i++) {
            // i == images인덱스 == imgLevel
            
            // 업로드 된 파일이 있는 경우
            if( !images.get(i).getOriginalFilename().equals("") ) { // 빈칸이 아니라면

                BoardImage img = new BoardImage(); //보드이미지 객체에 바뀐내용들을 담는다

                img.setImgPath(webPath); // 웹 접근 경로
                img.setImgName(Util.fileRename(images.get(i).getOriginalFilename())); // 변경된 파일명
                img.setImgOriginal( images.get(i).getOriginalFilename()); // 원본 파일명
                img.setImgLevel(i); // 이미지 레벨
                img.setBoardNo( board.getBoardNo()); // 게시글 번호

                imgList.add(img); // 옮겨진 보드이미지 내용을 imgList에 넣는다
            }
        } // for end

        //  5) imgList가 비어있지 않은 경우

        // 향상된 for문으로 반복 접근할 List가 비어있다면 for문은 수행되지 않는다.
        for(BoardImage img : imgList) {

            // 서로 다른 행을 일괄적으로 update하는 방법이 없기때문에
            // 한 행씩 수정
            result = dao.updateBoardImage(img); // 한 행씩 접근해서 업데이트한다
            // 결과 1 -> 기존에 저장된 이미지가 수정되었다.
            // 결과 0 -> 기존에 저장되지 않은 이미지가 추가 되었다 ->insert 진행

            // 새로운 이미지가 추가되었다 -> INSERT
            if(result == 0) {
                result = dao.insertBoardImage(img);

                if(result == 0) {
                    // 사용자 정의 예외
                    throw new UpdateBoardFailException("이미지 삽입 중 문제가 발생하였습니다.");
                }
            }
        }// for end

        // 6) 전달 받은 images 중 업로드된 파일이 있는 부분을 실제 파일로 저장 
        // ( 전달 받은 images 는 imgList에 저장되어있다)
        if(!imgList.isEmpty()) { // 파일이 있다면  꺼내서저장한다

            try { 
                for(int i =0; i<imgList.size(); i++) {

                    // i번째 인덱스가 아니라 imgList.get(i).getImgLevel()의 번호를 얻어온다
                    images.get(imgList.get(i).getImgLevel())
                    .transferTo(new File(serverPath + "/" + imgList.get(i).getImgName() ));
                }
            } catch (Exception e) {
                e.printStackTrace();

                // Runtime 예외가 발생할까봐  예외처리한것
                // 사용자 정의 예외(RuntimeException)
                throw new UpdateBoardFailException();
            }
        }
    } // if end

    return result;
}

- deleteImages

deleteImages로 파라미터를 넘길 때  넘겨야하는데 마이바티스는 SQL 수행 시 파라미터를 1개만 받을 수 있다. 전달할 파라미터가 다수인 경우 Map과 같은 컬렉션 객체를 이용하면 된다.

해결법 : map을 이용해 두 개의 값을 넘긴다 (List도 되지만 Map이 좀 더 쉽다)

타입을 오브젝트로 한다는건 모든 값을 받는다 (업캐스팅되어 모든게 다 받아진다)

 

- 6) 게시글 이미지 수정에서

update, insertBoardImage를 처리한 후 혹시 모를 예외처리를 위해 @Transactional & updateBoardFailException처리

 

InsertBoardFailException

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

// 사용자 정의 예외
public class InsertBoardFailException extends RuntimeException{
	
	// 기본 생성자
	public InsertBoardFailException() {
		super("게시글 삽입 과정에 문제가 발생하였습니다.");
	}
	
	// 매개변수 message 
	public InsertBoardFailException(String message) {
		super(message);
	}
}

UpdateBoardFailException

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

// 사용자 정의 예외
public class UpdateBoardFailException extends RuntimeException{
	
	// 기본 생성자
	public UpdateBoardFailException() {
		super("게시글 수정 과정에 문제가 발생하였습니다.");
	}
	
	// 매개변수 message 
	public UpdateBoardFailException(String message) {
		super(message);
	}
}

BoardDAO

/** 게시글 수정
 * @param board
 * @return result
 */
public int updateBoard(Board board) {
    return sqlSession.update("boardMapper.updateBoard", board);
}

public int deleteImages(Map<String, Object> map) {
    return sqlSession.delete("boardMapper.deleteImages", map);
}

/** 게시글 이미지 수정
 * @param img
 * @return result
 */
public int updateBoardImage(BoardImage img) {
    return sqlSession.update("boardMapper.updateBoardImage", img);
}


/** 게시글 새로운 이미지 삽입
 * @param img
 * @return result
 */
public int insertBoardImage(BoardImage img) {
    return sqlSession.insert("boardMapper.insertBoardImage", img);
}

board-mapper

<!-- 게시글 수정 -->
<update id="updateBoard" parameterType="Board">
    UPDATE BOARD SET 
    BOARD_TITLE = #{boardTitle},
    BOARD_CONTENT = #{boardContent},
    CATEGORY_CD = #{categoryCode},
    MODIFY_DT = SYSDATE
    WHERE BOARD_NO = #{boardNo}
</update>

<!-- 기존에 있었지만 삭제된 이미지 DELETE  
    파라미터 타입이 Map인 경우 : #{Key}를 작성하면 value가 출력된다 -->
<delete id="deleteImages" parameterType="map">
    DELETE FROM BOARD_IMG
    WHERE BOARD_NO = #{boardNo}
    AND IMG_LEVEL IN (${deleteImages})
</delete>

<!-- 게시글 이미지 수정-->
<update id="updateBoardImage">
    UPDATE BOARD_IMG SET
    IMG_PATH = #{imgPath},
    IMG_NM = #{imgName},
    IMG_ORIGINAL = #{imgOriginal}
    WHERE BOARD_NO = #{boardNo}
    AND IMG_LEVEL = #{imgLevel}
</update>


<!-- 게시글 수정 새로운 이미지 추가 -->
<insert id="insertBoardImage">
    INSERT INTO BOARD_IMG VALUES
    (SEQ_IMG_NO.NEXTVAL, #{imgPath}, #{imgName}, #{imgOriginal}, #{imgLevel}, #{boardNo})
</insert>

삭제하는 구문 이후에

새로운 이미지 추가와 기존이미지 수정이 동시에 진행되어야한다

 

반응형
저작자표시 비영리 변경금지 (새창열림)

'Backend > Spring' 카테고리의 다른 글

[Spring] 댓글 테이블, 클래스 구조 생성  (0) 2022.01.19
[Spring] 게시판 글 삭제  (0) 2022.01.19
[Spring] 게시글 (상세조회)  (0) 2022.01.10
[Spring] 기본 세팅  (0) 2022.01.10
[Spring] 게시판 조회(목록 조회)  (0) 2022.01.08
    'Backend/Spring' 카테고리의 다른 글
    • [Spring] 댓글 테이블, 클래스 구조 생성
    • [Spring] 게시판 글 삭제
    • [Spring] 게시글 (상세조회)
    • [Spring] 기본 세팅
    Deeb
    Deeb

    티스토리툴바