본문으로 바로가기

수정과 삭제를 어렵게 생각하지 않아도 된다. 정보를 가져오고 글을 작성하는 것과 차이가 별로 없다. 그대로 응용한다고 생각하면 된다. 수정은 수정페이지에 글의 상세정보를 가져오고 사용자가 작성한 글을 update 하면 된다. 여기서 상세정보를 가져올 때에는 조회수를 증가시키면 안된다. 삭제는 delete 쿼리를 이용해서 칼럼을 삭제한다. 하지만 나는 직접 삭제하지않고, db에는 남기고 리스트에서만 보이지 않게 할 것이다. 그러기 위해서는 db에 del_chk 라는 컬럼을 이용하여 삭제를 눌렀을 때, 해당 컬럼을 'Y'로 update하고 글 목록 리스트에선 del_chk 컬럼이 'N'인 즉, 사용자가 삭제하지 않은 글만 보이게 할 것이다. 이게 귀찮으면 그냥 delete 하면 된다~! 개발하고 싶은대로 하면 된다.



1. DB

1
2
3
4
5
6
7
8
9
10
11
CREATE TABLE `tb_board` (
  `IDX` int(11NOT NULL AUTO_INCREMENT,
  `PRE_IDX` int(11DEFAULT NULL,
  `TITLE` varchar(100NOT NULL,
  `CONTENTS` varchar(4000NOT NULL,
  `HIT_CNT` int(11NOT NULL DEFAULT '0',
  `DEL_CHK` varchar(1NOT NULL DEFAULT 'N',
  `CREA_DATE` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `CREA_ID` varchar(30NOT NULL,
  PRIMARY KEY (`IDX`)
ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8
cs

- 07행: delete 여부를 알 수 있는 컬럼이다.

- 08행: default를 수정했다.

이전의 DB를 생성했을 때 작성일(CREA_DATE) 컬럼에서 DEFAULT를 UPDATE ON CURRENT_TIMESTAMP 이렇게 설정했었다. 이렇게 해 놓으면 글 그대로 업데이트 될 때마다 수정한 시각이 기록된다. 수정한 작성일을 기록하고 싶으면 그대로 둬도 된다. 나는 수정을 하든 말든 작성한 날을 기록하고 싶기 때문에 default를 다음과 같이 바꾸었다.



2. 수정 버튼 생성

수정 버튼은 상세페이지에 만들었다.


boardDetail.jsp

1
<a href='<c:url value='/board/boardUpdate?idx=${detail.IDX }'/>' >수정</a>
cs

주소창에 해당 글의 번호를 파라미터로 함께 날려주었다. 여기서 파라미터 이름(idx) 대소문자 그리고 오타에 유의하자! 쿼리문에 쓰는 것과 동일해야 한다.



3. 수정페이지 생성

작성 페이지를 그대로 가져와서 몇가지만 수정했다.

 

boardUpdate.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div class="container col-md-6">
    <form action='<c:url value='/board/boardUpdate'/>' method="post">
        <div class="form-group">
            <label for="exampleFormControlInput1">제목</label>
            <input type="text" class="form-control" id="exampleFormControlInput1" name="title" value="${detail.TITLE }">
        </div>
        <div class="form-group">
            <label for="exampleFormControlInput1">작성자</label>
            <input type="text" class="form-control" id="exampleFormControlInput1" name="cre_id" value="${detail.CREA_ID }" readonly>
        </div>
        <div class="form-group">
            <label for="exampleFormControlTextarea1">내용</label>
            <textarea class="form-control" id="exampleFormControlTextarea1" rows="10" name="contents">${detail.CONTENTS }</textarea>
        </div>
        <input type="hidden" name="idx" value="${detail.IDX }">
        <button type="submit" class="btn btn-info">수정하기</button>
        <button type="button" class="btn btn-secondary">뒤로가기</button>
    </form>
</div>
cs

- 02행: url을 매핑해주었고 POST형태로 컨트롤러에 보낸다.

- 05행: name 속성을 지정해주고, value에 제목을 넣었다. 사용자가 제목과 내용중에서 제목만 수정하고 '수정하기'버튼을 누를 수도 있다. 그럴때 수정되지 않은 내용도 파라미터에 담겨서 넘어가기 위해 value에 값을 넣은 것이다.

- 09행: 마찬가지로 name 속성을 지정하고 value에 넣었다. 하지만 작성자는 수정할 수 없게 만들기 위해 readonly를 넣엇다.

- 13행: 마찬가지이다.

- 15행: hidden을 이용해서 글번호(idx)값도 같이 날려주었다. 글번호를 이용하여 update를 진행한다.

- 16행: 수정하기 버튼의 type을 submit으로 해주었다. submit이 아니면 form 태그가 작동을 안한다.



4. BoardController.java ("/board/boardUpdate")

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    @RequestMapping(value="/board/boardUpdate")
    public ModelAndView boardUpdate(CommandMap commandMap) throws Exception {
        
        ModelAndView mv = new ModelAndView("/board/boardUpdate");
        Map<String, Object> detail = boardService.selectBoardDetail(commandMap.getMap());
        mv.addObject("detail",detail);
        return mv;
    }
    
    @RequestMapping(value="/board/boardUpdate", method=RequestMethod.POST)
    public ModelAndView boardUpdatePOST(CommandMap commandMap) throws Exception {
        
        ModelAndView mv = new ModelAndView("redirect:/board/boardDetail");
        mv.addObject("idx", commandMap.get("idx"));
        boardService.updateBoard(commandMap.getMap());
        return mv;
    }
cs

- 01행: 수정페이지를 보여주는 컨트롤러이다.

- 05~06행: 글의 상세정보를 가져와 "detail"이란 이름에 저장시킨다.

- 10행: POST 형태인 /board/boardUpdate 를 뜻한다.

- 13~14행: 수정 후, 상세 페이지로 리다이렉트 시킨다. 여기서 상세페이지로 가기 위해선 글번호가 필요하기 때문에 글 번호 파라미터도 같이 넘겨준다.

- 15행: 수정한 글을 update 시키는 부분이다.



5. Service 영역

수정 페이지는 상세 페이지처럼 글 정보를 전부 가져온다. 그렇다면 상세페이지에 썼던 service를 그대로 써도 되지 않을까? 상관없다. 같은 기능을 한다면 service를 그대로 가져와도 전혀 상관없다. 하지만 나는 상세페이지의 service는 조회수를 증가시켜주는 로직이 들어가있다. 수정페이지에 접근할때에는 조회수를 증가시키면 안되기때문에 상세페이지에 썼던 service는 쓰지 않을 것이다.


5-2. BoardService.java

1
2
Map<String, Object> selectBoardDetail(Map<String, Object> map);
void updateBoard(Map<String, Object> map);
cs


5-3. BoardServiceImpl.java

1
2
3
4
5
6
7
8
9
10
11
    @Override
    public Map<String, Object> selectBoardDetail(Map<String, Object> map) {
        // TODO Auto-generated method stub
        return boardDAO.detailBoard(map);
    }
 
    @Override
    public void updateBoard(Map<String, Object> map) {
        // TODO Auto-generated method stub
        boardDAO.updateBoard(map);
    }
cs



6. BoardDAO

글의 상세정보를 가져오는 건 같은 쿼리를 쓰기때문에 상세페이지에서도 썼던 DAO를 그대로 쓸 것이다. 


BoardDAO.java

1
2
3
4
5
6
7
8
9
10
    @SuppressWarnings("unchecked")
    public Map<String, Object> detailBoard(Map<String, Object> map) {
        // TODO Auto-generated method stub
        return (Map<String, Object>) selectOne("board.detailBoard", map);
    }
 
    public void updateBoard(Map<String, Object> map) {
        // TODO Auto-generated method stub
        update("board.updateBoard",map);
    }
cs

- 01~05행: 상세페이지때 작성한 부분이기 때문에 또 작성하지 않아도 된다.



7. board_sql.xml

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
32
33
    <select id="detailBoard" resultType="hashmap">
        <![CDATA[
            SELECT
                IDX,
                TITLE,
                CONTENTS,
                HIT_CNT,
                CREA_ID,
                IF(
                    DATE_FORMAT(CREA_DATE, '%Y%m%d') < DATE_FORMAT(now(),'%Y%m%d'),
                    DATE_FORMAT(CREA_DATE, '%Y.%m.%d'),
                    DATE_FORMAT(CREA_DATE, '%H:%i')
                ) as CREA_DATE
            FROM
                TB_BOARD
            WHERE
                IDX = #{ idx}
        ]]>
    </select>
 
    <update id="updateBoard" parameterType="hashmap">
        <![CDATA[
            UPDATE
                TB_BOARD
            SET
                TITLE = #{ title},
                CONTENTS = #{ contents}
            WHERE
                IDX = #{ idx}
                and
                CREA_ID = #{ crea_id}
        ]]>
    </update>
cs

- 01~19행: 상세페이지때 작성한 것이기 때문에 또 작성하지 않아도 된다.

- 21행: 수정한 글을 업데이트 시키는 부분이다.



8. 실행


상세보기에 수정 버튼을 만들었다. 수정을 버튼을 눌러서 해당글을 수정해보자.



수정페이지에 가게 되면, 글의 정보들이 그대로 적혀져있을 것이다. 수정하고 싶은 부분을 수정하고 수정하기 버튼을 눌러보자. 나는 제목을 수정해보았다.


수정하기 버튼을 누르면 상세페이지로 가게 되고, 수정한 부분이 보일 것이다.



이제 마지막으로 남은 것은 삭제이다. 삭제까지 다 하게되면 CRUD가 적용된 게시판을 완성하게 된다. 그리고 여기까지 잘 따라왔다면 삭제부분은 다른 설명이 없어도 무리없이 따라 올 수 있을 것이다.


9. 삭제 버튼 생성

삭제 버튼도 상세페이지에 만들었다.

1
<a href='<c:url value='/board/boardDelete?idx=${detail.IDX }'/>' >삭제</a>
cs

수정과 마찬가지로 삭제를 위해선 글 번호가 필요하다. 그래서 주소창에 글 번호를 파라미터로 넘겨주었다.



10. BoardController.java ("/board/boardDelete")

1
2
3
4
5
6
    @RequestMapping(value="/board/boardDelete")
    public ModelAndView boardDelete(CommandMap commandMap) throws Exception {
        ModelAndView mv = new ModelAndView("redirect:/board/boardList");
        boardService.deleteBoard(commandMap.getMap());
        return mv;
    }
cs

- 03행: 삭제 후에 목록으로 리다이렉트 시켜준다.

- 04행: 파라미터가 잘 왔는 지 확인하려고 만든 거다. 생략해도 상관 없다.

- 05행: 삭제를 해주는 부분이다.



11. Service 영역

삭제에는 두가지 방법이 있다. 첫번째는 데이터베이스에서 삭제하는 방법과 두번째는 삭제 컬럼으로 안보이게 하는 방법이 있다. 둘 중 원하는 방법을 사용하면 된다. 둘 다 각자 장단점이 있다. 나는 삭제 컬럼을 만들어서 목록에서 보이지 않게 할 것이다. 즉, 목록에는 보이지 않지만 데이터베이스에는 존재한다.


11-1. BoardService.java

1
    void deleteBoard(Map<String, Object> map);
cs


11-2. BoardServiceImpl.java

1
2
3
4
5
    @Override
    public void deleteBoard(Map<String, Object> map) {
        // TODO Auto-generated method stub
        boardDAO.deleteBoard(map);
    }
cs



12. BoardDAO.java

1
2
3
4
    public void deleteBoard(Map<String, Object> map) {
        // TODO Auto-generated method stub
        update("board.deleteBoard", map);
    }
cs

삭제 컬럼으로 목록에서 안보이게 하기 때문에 update를 사용해서 삭제 컬럼을 update 시킨다. 만약 데이터베이스에서 완전히 삭제를 하려고 한다면, delete를 사용하면 된다.



13. board_sql.xml

1
2
3
4
5
6
7
8
9
10
    <update id="deleteBoard">
        <![CDATA[
            UPDATE
                TB_BOARD
            SET
                DEL_CHK = 'Y'
            WHERE
                IDX = #{ idx}
        ]]>
    </update>
cs

삭제 컬럼을 'N'에서 'Y'로 업데이트 해준다. 삭제컬럼의 기본이 'N'이다. 목록에서는 삭제컬럼이 'N'인 글만 보이게 했다. 따라서 삭제버튼을 눌러서 삭제컬럼을 'Y'로 업데이트 시킨 글들은 보이지 않게 된다. 하지만 데이터베이스에는 글의 정보들이 남아있다. 



14. 실행


삭제버튼을 상세보기 버튼에 만들었다. 원하지 않는 글을 삭제해보자. 나는 7번글인 fdas를 삭제할 것이다.



삭제버튼을 누르면 목록으로 가게 되고, 목록에서 해당 글이 사라진 것을 확인할 수 있다.



이제 게시판 만들기가 끝났다. 읽고, 쓰고, 수정하고, 삭제까지 가능한 CRUD 게시판이 완성되었다. 하지만 슬프게도 여기서 끝난게 아니다. 이거를 게시판이라고 내놓기엔 많이 엉성하고 이상하다. 페이징 기능, 파일 등록, 글의 권한이 있는 이용자에게 보이는 수정과 삭제 버튼 등등등 원하는 기능을 넣으면서 좀 더 다듬어야 한다. 그리고 여기까지 잘 따라왔고 이해했다면 이제 스프링은 큰 문제없이 사용할 수 있다. 다른 복잡한 무엇이 있다하더라도 기본적으로 이 CRUD 게시판의 연장선이라고 생각하면 된다.