YJ의 새벽

Spring 17 ( 게시글 작성/수정) 본문

Spring/Spring

Spring 17 ( 게시글 작성/수정)

YJDawn 2023. 5. 8. 18:40

 

 

 

 

 

 

 

 

 

 

-- 게시글 작성과, 수정이 쿼리파라미터 (  mode-insert /  mode-update  ) 로 구분된다.

-- 일단 먼저, 글쓰기 버튼을 눌렀을때, 받는 주소를 연결해주자.  ( 글목록 글쓰기 버튼 )

 

boardList.jsp
글쓰기 눌렀을때 주소. mode=insert

 

-- 수정 버튼 눌렀을때, 받는 주소 수정.  (  글목록 타이틀 클릭후 나오는 수정 버튼 )

boardDetail.jsp
수정 눌렀을때 주소.  mode=update

 

 

 

 

---- 수정버튼 누르면,  다시 글쓰기로 ! 이동하는걸 확인하자. 

 

 

 

 

 

---- BoardController  에서,  GetMapping. 

--- 글쓰기 버튼으로 들어온 글쓰기폼과,

    수정하기버튼으로 들어온 글수정폼을  하나의 컨트롤러로 맵핑해주었다.

--- '수정하기' 버튼으로 들어왔다면  ( mode='update' )

     미리 게시글정보를 받아온 service 를 호출하여 데이터를 깔아주자.  ( title, image , content )

--- '글쓰기' 버튼으로 들어왔다면 ( mode='insert' )

     주소값에 ( 쿼리파라미터 no )  가 없을수도 있으니,  기본값 ( defaulValue ) 을 넣어둔다.

 

 

 

boardWriteForm.jsp

 

 

 

------------------------------------------------------------------------------------------------ 

 

 

---게시글을 작성해보자 .

---- boardTitle ,   images[0~5]  ,  boardContent   의 파라미터를 얻어온다.

 

 

 

 

 

---boardController .    서비스 넘어가기전 컨트롤러 클래스.

--1)  로그인한 회원번호 얻어와서 . 미리 detail VO 에 받아놓자 . (  클래스위에 SessionAttributes )

--2) 이미지 저장경로 얻어오기 ( webPath, folderPath )

--3) 글의 삽입인지?  수정인지?    if  ( mode=='insert' )

      -- 글의 삽입( mode =' insert' ) 이라면 .  게시판 부분삽입 ( 제목, 내용, 회원번호 , 게시판코드 ) (insert)

          후 게시글번호 ( boardNo ) 을 반환받아.  이를 이용 이미지를 넣자.(insert)

      -- 두번의 insert 중 한번이라도 실패하면 전체 rollback 트랜잭션 처리 !!

--4) 글의 수정 ( mode='update' ) 이라면 ??

       -- 쿼리파라미터로 게시글의 번호가 따라온다. 수정결과만 반환받자.


// 글쓰기 ( 등록 )
@PostMapping("/write/{boardCode}")
public String boardWrite( @PathVariable("boardCode") int boardCode,
                        @ModelAttribute BoardDetail detail,  // boardTitle,content,boardNo( hidden에 수정)
                        @RequestParam (value="images",required = false) List<MultipartFile> imageList, // 업로드이미지 리스트
                        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
                        ) throws IOException{
    // 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")) {  // 삽입

        // 게시글 부분 삽입  (  제목,내용,회원번호,게시판코드 )
        // --> 삽입된 게시글의 번호 (boardNo) 반환 , 삽입이 끝나면 게시글상세조회로 리다이렉트 
        // 게시글에 포함된 이미지 정보 삽입 ( 0~5 개, 게시글 번호 필요 )
        // --> 실제 파일로 변환해서 서버에 저장 
        // 두번의 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");   
            message = "게시글 삽입 실패..";
        }
        ra.addFlashAttribute("message",message);
        return "redirect:"+ path;
    }else { // "update" 수정
        // 게시글 수정 서비스 호출
        // 게시글 번호를 알고있기때문에 수정결과만 반환받으면 된다.
        int result = service.updateBoard(detail, imageList, webPath,folderPath, deleteList);

        String path = null;
        String message = null;

        if ( result > 0 ) {
            //    /board/write/{boardCode} 
            //--> /board/detail/{boardCode}/{boardNo}?cp=10
            path ="../detail/" +boardCode+ "/" +detail.getBoardNo() + "?cp="+ cp ;
            message = "게시글이 수정!! 되었습니다";
        }else {
            path = req.getHeader("referer");   
            message = "게시글 수정!! 실패..";
        }
        ra.addFlashAttribute("message",message);
        return "redirect:"+ path;
    }
}

 

 

 

 

---boardService 인터페이스.  

	//  게시글 삽입 + 이미지 삽입
	int insertBoard(BoardDetail detail, List<MultipartFile> imageList, String webPath, String folderPath) throws IOException;

	//   게시글 수정 서비스.
	int updateBoard(BoardDetail detail, List<MultipartFile> imageList, String webPath, String folderPath,
			String deleteList) throws IOException; 
}

 

 

 

 

--- Controller 에서 넘어온  boardServiceImpl  , 서비스 클래스 .

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

// Spring 에서 트랜잭션 처리하는 방법 ?! 

// *** AOP (관점 지향 프로그래밍) 을 이용해서 DAO -> Service 또는 Service코드 수행 시점에
//     예외가 발생했을때 rollback 을 수행
// 방법 1 )))) <tx:advice> XML 을 이용한 방법 -> 패턴을 지정하여 일치하는 메서드 호출 시 자동으로 트랜잭션 제어

// 방법 2 )))) @Transactional 선언적 트랜잭션 처리방법
//       --> RuntimeException ( Unchecked Exception) 처리를 기본값으로 갖음. 

//  checked Exception  :  예외처리가 필수 (transFerTo()) -->  SQL 관련 예외, 파일업로드 관련예외
//unchecked Exception  :  예외처리가 선택 ( int a = 10/0; )

//  rollbackFor  :  rollback 을 수행하기 위한 예외의 종류를 작성
@Transactional(rollbackFor = { Exception.class })
@Override
public int insertBoard(BoardDetail detail, List<MultipartFile> imageList, String webPath, String folderPath) throws IOException {

    //  1. 게시글 삽입 . 

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

    if ( boardNo > 0 ) {
        // 2.  이미지 삽입 실행 

        // imageList : 실제 파일이 담겨있는 리스트 .
        // boardImageList : DB 에 삽입할 이미지 정보만 담겨있는 리스트. 
        // reNameList  : 변경될 파일명이 담겨있는 리스트.
        List<BoardImage> boardImageList = new ArrayList<BoardImage>();
        List<String> reNameList = new ArrayList<String>();

        // imageList 에 담겨있는 파일정보 중 실제 업로드된 파일만 분류하는 작업.
        for (int i=0; i < imageList.size(); i++) {

            if ( imageList.get(i).getSize() > 0 ) {  // i 번째 요소에 업로드된 이미지가 있을경우
                // 변경된 파일명 저장. ( 20250343013_20 )
                String reName = Util.fileRename( imageList.get(i).getOriginalFilename() );
                reNameList.add(reName);

                // BoardImage 객체 생성하여 값 세팅후 boardImageList 추가 .
                BoardImage img = new BoardImage();
                img.setBoardNo(boardNo);  // 게시글번호
                img.setImageLevel(i);  // 이미지 순서 ( 0,1,2,3,4  )
                img.setImageOriginal(imageList.get(i).getOriginalFilename());  // 원본 파일명
                img.setImageReName( webPath+reName); // 웹접근경로 + 변경된 파일명

                boardImageList.add(img);
            }
        }
        // 분류작업 종료 후 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.

                // 강제를 예외를 발생시켜 rollback 수행하게 함.
                // --> 사용자 정의 예외 만들기.
                throw new InsertFailException();
            }
        }
    }
    return boardNo;
}





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

    //  1)  XSS 방지 처리 + 개행문자 처리
    detail.setBoardTitle( Util.XSSHandling( detail.getBoardTitle() ));
    detail.setBoardContent( Util.XSSHandling( detail.getBoardContent()));
    detail.setBoardContent( Util.newLineHandling( detail.getBoardContent() )); // 개행문자 처리

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

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

        // imageList 에 담겨있는 파일정보 중 실제 업로드된 파일만 분류하는 작업.
        for (int i=0; i < imageList.size(); i++) {

            if ( imageList.get(i).getSize() > 0 ) {  // i 번째 요소에 업로드된 이미지가 있을경우
                // 변경된 파일명 저장. ( 20250343013_20 )
                String reName = Util.fileRename( imageList.get(i).getOriginalFilename() );
                reNameList.add(reName);

                // BoardImage 객체 생성하여 값 세팅후 boardImageList 추가 .
                BoardImage img = new BoardImage();
                img.setBoardNo(detail.getBoardNo());  // 게시글번호
                img.setImageLevel(i);  // 이미지 순서 ( 0,1,2,3,4  )
                img.setImageOriginal(imageList.get(i).getOriginalFilename());  // 원본 파일명
                img.setImageReName( webPath+reName); // 웹접근경로 + 변경된 파일명

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

            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 -> 수정 O -> 기존 이미지가 있었다.
                // 결과 2 -> 수정 X -> 기존 이미지가 없었다. --> insert 작업.수행

                // 6) update 실패하면 insert . 
                if ( result == 0 ) {
                    result = dao.insertBoardImage(img);
                    // -> 값을 하나씩 대입해서 삽입하는경우 , 결과가 0이 나올수는 없다.!
                    // 단, 예외 (제약조건,sql문법오류) 는 발생할수 있다.
                }
            }
            // 7) 업로드된 이미지가 있다면 서버에 저장. 
            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)));
                }
            }
        }
    }
    return result;
}

 

---boardDAO

	// 게시글 삽입 DAO 
	public int insertBoard(BoardDetail detail) {
		int result = sqlSession.insert("boardMapper.insertBoard", detail);  // 0 or 1
		
		if ( result > 0 )  result = detail.getBoardNo();   // 이미지넣기위한 boardNo 반환.
		
		return result;
	}
	// 게시글 이미지 삽입 DAO 
	public int insertBoardImageList(List<BoardImage> boardImageList) {
		return sqlSession.insert("boardMapper.insertBoardImageList",boardImageList);
	}

	// 게시글 수정 DAO
	public int updateBoard(BoardDetail detail) {
		return sqlSession.update("boardMapper.updateBoard",detail);
	}

	// 게시글 이미지 삭제 DAO 
	public int deleteBoardImage(Map<String, Object> map) {
		return sqlSession.delete("boardMapper.deleteBoardImage",map);
	}

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

	// 게시글 이미지 1개 삽입
	public int insertBoardImage(BoardImage img) {
		return sqlSession.insert("boardMapper.insertBoardImage",img);
	}

 

 

---board-mapper

<!--  게시글 삽입 -->
	<!-- 
		useGeneratedKeys 속성 : DB내부적으로 생성한 키 (ex 시퀀스 ) 를 받는
						JDBC getGeneratedKeys() 메서드를 사용하도록 설정. ( 기본값 false )		
		
		동적 SQL 중 <selectKey>
		- <selectKey>  :  INSERT/UPDATE에 사용된 키 (ex 시퀀스 ) 를 원하는 변수/필드에 담아 반환하는태그
		- keyProperty  :  Key를 담을 변수/필드 지정하는 속성 
		- order  :  INSERT/UPDATE 에 작성된 메인 SQL 이 수행되기 전(BEFORE) 또는 후(AFTER) 에
		          <selectKey> 내부 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>
	
	
	<!-- 게시글 수정 -->
	<update id="updateBoard">
		UPDATE BOARD SET
		BOARD_TITLE = #{boardTitle},
		BOARD_CONTENT = #{boardContent},
		UPDATE_DT = SYSDATE
		WHERE BOARD_NO = #{boardNo}
	</update>

	<!-- 게시글 이미지 삭제 (IN 구문작성시 삭제되는 deleteList에 ' ' 가 없도록 주의) -->
	<delete id="deleteBoardImage">
		DELETE FROM BOARD_IMG
		WHERE BOARD_NO = ${boardNo}
		AND IMG_LEVEL 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>
	
	
</mapper>

 

 

 

 

 

 

--- 실행이 안됐을때 생성한 예외클래스 .

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Comments