수정화면 전환
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 |