프로그래밍/Spring

스프링 부트 게시판 만들기4 - Rest방식의 게시글 목록/작성/수정/삭제

dev109 2016. 10. 17.
반응형

관련글 : 

스프링 부트로 게시판 만들기1 - 프로젝트 생성

스프링 부트로 게시판 만들기2 - MyBatis, MySQL 연동

스프링 부트로 게시판 만들기3 - JSP 사용하기



사실 포스팅은 매일하는데 어제는 작성 완료를 못해서 못올렸네요. 문제가 있었는데요.

스프링 부트로 게시판 만들기1에서 목표가 rest서비스를 하겠다.... 였습니다. 스프링 부트도 처음 접해보는데 개념이 잡히지 않은 rest방식으로 하려니까 자료도 별로 없어서 힘들더라고요. 지식과 자료가 없는 총체적난국ㄷㄷㄷ

rest 서비스를 알아보다가 제가 제일 의아해했던건 같은 요청을 하는데 컨트롤러에서 어떻게 GET 혹은 PATCH 등등으로 받아들이는가... 였습니다. 서버단에서 header를 읽어온다고 해도 웹 브라우저에서 PATCH, DELETE방식으로 요청을 하는지가 가장 큰 문제였네요. 


아래에 표를 보시면

 

 요청

 Method

 기능

게시글 목록 

 /board

 GET

 글 목록을 가져온다

게시글 상세

 /board/1

 GET

 1번 게시글을 본다

게시글 작성 페이지로 이동

 /board/post

 GET

 글 작성 페이지로 이동한다

게시글 작성

 /board/post

 POST 글을 작성한다

게시글 수정 페이지로 이동

 /board/post/1

 GET

 1번 게시글 수정 페이지로 이동한다

게시글 수정

 /board/post/1

 PATCH or PUT

 1번 게시글을 수정한다

게시글 삭제

 /board/post/1

 DELETE

 1번 게시글을 삭제한다


어제 게시글 수정기능까지는 다 구현했는데 삭제부터 문제가 생겼습니다. 표에 보시다시피 게시글 작성 페이지로 이동과 게시글 작성의 요청이 같죠?(/board/post) 하지만 이건 웹 브라우저에서 GET방식, POST방식으로 구분해서 요청하면 컨트롤러에서 @RequestMapping의 RequestMethod로 구분해주면 되기때문에 별거 없지요. 문제는 게시글 수정 페이지로 이동, 게시글 수정, 게시글 삭제입니다. 이 세가지도 요청이 같아요.(/board/post/1) GET방식은 문제가 되지 않지만 PUT과 DELETE 방식이 문제였습니다. 어떻게 웹 브라우저에서 PUT, DELETE로 요청을 해야 하는가.... 스프링4.0 책을 뒤져보다 답을 찾기는 했는데 썩 마음에 들지가 않네요^^; 자세한 방법이나 마음에 들지 않는 이유는 아래에서 다루도록 하겠습니다.





그럼 이전 포스트에 이어서 jstl을 이용해서 게시판 리스트를 제대로 출력해볼까요?.


views - boardList.jsp

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
34
35
36
<%@ 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="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta http-equiv="Content-Type" content"text/html; charset=UTF-8">
<!-- BootStrap CDN -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap-theme.min.css">
<title>게시글 목록</title>
</head>
<body>
    <h3>게시글 목록</h3>
    <button class="btn btn-primary" style="float : right;" onclick="location.href='/board/post'">작성</button>
    <table class="table">
        <tr>
            <th>No</th>
            <th>제목</th>
            <th>작성자</th>
            <th>작성날짜</th>
            <th>조회수</th>
        </tr>
        <c:forEach var="board" items="${list}">
        <tr>
            <td>${board.bno}</td>
            <td><a href="/board/${board.bno}">${board.subject}</a></td>
            <td>${board.writer}</td>
            <td><fmt:formatDate value="${board.reg_date}" pattern="MM/ dd" /></td>
            <td>${board.hit}</td>
        </tr>
        </c:forEach>
    </table>
</body>
</html>
cs



jstl사용을 위한 taglib과 부트스트랩 사용을 위해 부트스트랩 CDN을 선언해주었습니다.

view만 수정했을 시 프로젝트를 재실행하지 않고 http://localhost:8080/board 로 요청 혹은 새로고침을 하면 바로 결과가 출력됩니다.






목록을 출력했으니 이제 글 작성 기능을 구현하겠습니다. 일단 글쓰기 버튼을 보면 button class인 btn btn-primary는 부트스트랩에서 제공하는 버튼입니다. 오른쪽으로 정렬되게 스타일을 지정했고 클릭시 board/post로 요청이 됩니다.



이제 controller를 작성해야 하는데.... 제가 이틀간 삽질(?)을 하면서 컨트롤러를 하나로 통합해버렸습니다....! 

BoardListController 이름을 BoardController로 수정후에 작성하시면 됩니다. (삭제하고 새로 만드셔도 괜찮습니다.)


BoardController.java

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.board.controller;
 
import java.util.List;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
 
import com.board.domain.BoardVO;
import com.board.mapper.BoardMapper;
 
@Controller
@RequestMapping("/board")
public class BoardController {
 
    @Autowired
    private BoardMapper boardMapper;
    
    
    //게시글 목록
    @RequestMapping(method=RequestMethod.GET)
    public ModelAndView list() throws Exception{
        
        List<BoardVO> list = boardMapper.boardList();
        
        return new ModelAndView("boardList","list",list);
    }
    
    //게시글 작성 페이지(GET)    
    @RequestMapping(value="/post",method=RequestMethod.GET)
    public ModelAndView writeForm() throws Exception{
        
        return new ModelAndView("boardWrite");
    }
    
    //게시글 작성(POST)
    @RequestMapping(value="/post",method=RequestMethod.POST)
    public String write(@ModelAttribute("BoardVO") BoardVO board) throws Exception{
 
        boardMapper.boardInsert(board);
        
        return "redirect://localhost:8080/board";
    }
}
 
cs


이전 포스트에서 작성한 BoardListController에서 뭐가 달라졌냐면.... 클래스 위에 @RestController가 아닌 @Controller로 수정하였고, 바로 아래에 @RequestMapping("/board")가 추가되었습니다. 클래스에 매핑을 해주면 상위경로로 매핑되기 때문에 아래에 게시글 작성을 위해 @RequestMapping("/post")만 작성해도 자동으로 /board/post 를 매핑해줍니다.

음 그리고 @RestController는 @Controller와 @ResponseBody가 합쳐진 어노테이션입니다. view가 필요없는 API만 지원하는 서비스에서 사용하면 되는데 저는 view가 필요하기 때문에 @Controller를 사용해야합니다. 만약 @RestController를 사용하면 화면에 리턴해준 "redirect://localhost:8080/board"가 그대로 출력됩니다.




views - boardWrite.jsp 생성 후 작성

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
34
<%@ 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="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta http-equiv="Content-Type" content"text/html; charset=UTF-8">
<!-- BootStrap CDN -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap-theme.min.css">
<title>게시글 작성</title>
</head>
<body>
    <h3>게시글 작성</h3>
    <div style="padding : 30px;">
        <form method="POST" action="/board/post">
          <div class="form-group">
            <label>제목</label>
            <input type="text" name="subject" class="form-control">
          </div>
          <div class="form-group">
            <label>작성자</label>
            <input type="text" name="writer" class="form-control">
          </div>
          <div class="form-group">
            <label>내용</label>
            <textarea name="content" class="form-control" rows="5"></textarea>
          </div>
          <button type="submit" class="btn btn-default">작성</button>
        </form>
    </div>
</body>
</html>
cs




프로젝트 재실행 후 새로고침을 하면 작성 버튼을 누르면 게시글을 작성할 수 있는 페이지로 넘어갑니다.




작성을 다 한 후에 작성버튼을 누르면 작성한 게시글이 DB에 저장되고 리턴값이 redirect:/board 이기 때문에 /board로 요청되어 바로 리스트 페이지로 넘어갑니다.


잘 나오나요? 이제 게시글 보기를 해볼게요.

이전 포스트에서 MyBatis와 MySQL을 연동하며 작성했던 mapper 인터페이스와 쿼리문은 게시글 리스트와 작성만 했기때문에 게시글 보기 기능을 위해 추가해줘야 합니다.

추가하는김에 수정, 삭제까지 추가하겠습니다.


com.board.mapper.BoardMapper.java 

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
package com.board.mapper;
 
 
import java.util.List;
 
import com.board.domain.BoardVO;
 
public interface BoardMapper {
 
    //글작성
    public void boardInsert(BoardVO vo)throws Exception;
    
    //글목록
    public List<BoardVO> boardList()throws Exception;
    
    //글보기
    public BoardVO boardView(int bno)throws Exception;
    
    //조회수 증가
    public void hitPlus(int bno)throws Exception;
    
    //글수정
    public void boardUpdate(BoardVO vo)throws Exception;
    
    //글삭제
    public void boardDelete(int bno)throws Exception;
}
 
cs




src/main/resources - mappers.boardMapper.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
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper 
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
 
<mapper namespace="com.board.mapper.BoardMapper">
 
    <insert id="boardInsert" parameterType="com.board.domain.BoardVO">
        insert into board_pro
            values
        (#{bno},#{subject},#{content},#{writer},now(),0)
    </insert>
    
    <select id="boardList" resultType="com.board.domain.BoardVO">
        select * from board_pro
    </select>
 
    <select id="boardView" parameterType="int" resultType="com.board.domain.BoardVO">
        select * from board_pro where bno = #{bno}
    </select>
    
    <update id="hitPlus" parameterType="int">
        update board_pro set hit = hit+1 where bno = #{bno}
    </update>
    
    <update id="boardUpdate" parameterType="com.board.domain.BoardVO">
        update board_pro set subject = #{subject}, content = #{content} where bno = #{bno}
    </update>
    
    <delete id="boardDelete" parameterType="int">
        delete from board_pro where bno = #{bno}
    </delete>
</mapper>
cs





com.board.controller.BoardConroller.java 에 글 상세보기를 위한 메서드를 추가해줍니다.(게시글 작성 아래에 추가해주시면 됩니다.)

1
2
3
4
5
6
7
8
9
//게시글 상세
@RequestMapping(value="/{bno}",method=RequestMethod.GET)
public ModelAndView view(@PathVariable("bno"int bno) throws Exception{
    
    BoardVO board = boardMapper.boardView(bno);
    boardMapper.hitPlus(bno);
    
    return new ModelAndView("boardView","board",board);
}
cs





views - boardView.jsp 생성 후 작성

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
34
35
36
37
38
39
40
41
42
43
44
45
46
<%@ 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="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta http-equiv="Content-Type" content"text/html; charset=UTF-8">
<!-- BootStrap CDN -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap-theme.min.css">
<title>게시글 상세</title>
</head>
<body>
    <h3>게시글 상세</h3>
    <div style="padding : 30px;">
      <div class="form-group">
        <label>제목</label>
        <span>${board.subject}</span>
      </div>
      <div class="form-group">
        <label>작성자</label>
        <span>${board.writer}</span>
      </div>
      <div class="form-group">
        <label>작성날짜</label>
        <span><fmt:formatDate value="${board.reg_date}" pattern="yyyy/ MM/ dd HH:mm" /></span>
      </div>
      <div class="form-group">
        <label>조회수</label>
        <span>${board.hit}</span>
      </div>
      <div class="form-group">
        <label>내용</label>
        <p>${board.content}</p>
      </div>
      <div class="form-group">
          <input type="button" value="수정" onclick='location.href="/board/post/${board.bno}"'>
          <form:form action="/board/post/${board.bno}" method="DELETE">
              <input type="submit" value="삭제">
          </form:form>
      </div>
    </div>
</body>
</html>
cs



BoardController의 view() 메서드에서 board라는 이름으로 BoardVO 객체를 리턴했기때문에 boardView.jsp에서도 ${board}로 받았고, 맨 위에 보시면 taglib이 하나 더 추가되었습니다.

1
2
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>



아래에 삭제버튼을 감싸고있는 <form:from>태그를 사용하기 위해 추가해주었는데요. 사용한 이유는....

웹 브라우저에는 GET과 POST방식만 지원하고 있어서 PUT,PATCH,DELETE방식의 요청은 전송할 수 없습니다. 그래서 스프링은 HiddenHttpMethodFilter를 이용해 PUT, PATCH, DELETE방식의 요청을 할 수 있도록 해주는데요. <form:form>태그의 method 속성 값으로 지정해주면 됩니다.

1
2
3
<form:form action="/board/post/${board.bno}" method="DELETE">
    <input type="submit" value="삭제">
</form:form>
cs

<for

<form:form>태그의 method값이 PUT,PATCH,DELETE인 경우에는 <form:form>가 hidden타입의 <input>태그를 추가로 생성합니다.

1
2
3
4
<form action="/board/post/${board.bno}" method="POST">
    <input type="hidden" name="_method" value="DELETE"/
    <input type="submit" value="삭제">
</form>
cs

이런식으로 생성이되어 전송된다고 생각하시면 됩니다. HiddenHttpMethodFilter는 요청 파라미터에 "_method"가 있을 경우 파라미터의 value를 요청 방식으로 사용하도록 스프링 MVC의 관련 정보를 설정하는 역할을 해줍니다. 그럼 컨트롤러에서 알맞는 요청의 메서드를 찾아주기 때문에 웹 브라우저를 이용하더라도 Restful 방식으로 구현된 컨트롤러를 이용할 수 있게됩니다. HiddenHttpMethodFilter설정은 아래에서 하고, 일단 게시글 상세 페이지로 이동하는지 확인해봐야겠죠?



게시글 상세 페이지로 이동하기 위해 게시글 목록에서 요청 url을 추가해줍니다.

boardList.jsp에서 제목부분만 수정

1
2
3
4
변경전
<td>${board.subject}</td>
변경후
<td><a href="/board/${board.bno}">${board.subject}</a></td>
cs




프로젝트를 재실행 후 새로고침 혹은 http://localhost:8080/board 요청하시고 제목을 클릭 혹은 http://localhost:8080/board/글번호 로 요청



너무나 허접하지만 게시판의 상세 내용이 잘 출력되네요^^; 버튼은 <form:form>태그 때문에 위아래로 나오는데 일단 기능이 중요하기때문에 바로 수정과 삭제 기능을 구현할게요.

게시글 상세에서 수정, 삭제 버튼은 만들어 놨으니 바로 Controller로 갑니다.


BoardController.java 에 추가해주세요.

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
//게시글 수정 페이지(GET)
@RequestMapping(value="/post/{bno}", method=RequestMethod.GET)
public ModelAndView updateForm(@PathVariable("bno"int bno) throws Exception{
        
    BoardVO board = boardMapper.boardView(bno);
    
    return new ModelAndView("boardUpdate","board",board);
}
    
//게시글 수정(PATCH)
@RequestMapping(value="/post/{bno}", method=RequestMethod.PATCH)
public String update(@ModelAttribute("BoardVO")BoardVO board,@PathVariable("bno"int bno) throws Exception{
        
    boardMapper.boardUpdate(board);
        
    return "redirect://localhost:8080/board/"+bno;
}
//게시글 삭제(DELETE)
@RequestMapping(value="/post/{bno}", method=RequestMethod.DELETE)
public String delete(@PathVariable("bno"int bno) throws Exception{
        
    boardMapper.boardDelete(bno);
        
    return "redirect://localhost:8080/board";
}
cs




BoardController.java 전체 소스




위에서 말했다시피 HiddenHttpMethodFilter를 사용해야 하니까 설정을 해줘야겠죠?


board/src/main/java/com/board/BoardApplication.java

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.board;
 
import javax.sql.DataSource;
 
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jersey.JerseyProperties.Filter;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.web.filter.HiddenHttpMethodFilter;
 
@SpringBootApplication
@MapperScan(value = {"com.board.mapper"})
public class BoardApplication {
 
    public static void main(String[] args) {
        SpringApplication.run(BoardApplication.class, args);
    }
    
    /**
     * SqlSessionFactory 설정
     */
    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource)throws Exception{
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
            sessionFactory.setDataSource(dataSource);
            
            Resource[] res = new PathMatchingResourcePatternResolver().getResources("classpath:mappers/*Mapper.xml");
            
            sessionFactory.setMapperLocations(res);
            
            return sessionFactory.getObject();
    }
    
    /**
     * HiddenHttpMethodFilter  
     */
    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
        HiddenHttpMethodFilter filter = new HiddenHttpMethodFilter();
        return filter;
    }
    
}
 
cs


스프링 부트에서 HiddenHttpMethodFilte 설정에 대한 글이 없어서 헤매긴했는데... 별거없더군요. HiddenHttpMethodFilter 객체를 생성해서 리턴해주면 끝...!^^;


게시글 수정 JSP도 작성해야겠죠?

boardUpdate.jsp

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
<%@ 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="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta http-equiv="Content-Type" content= "text/html; charset=UTF-8">
<!-- BootStrap CDN -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap-theme.min.css">
<script src="//code.jquery.com/jquery.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<title>게시글 수정</title>
</head>
<body>
    <h3>게시글 수정</h3>
    <div style="padding : 30px;">
        <form:form commandName="BoardVO" action="/board/post/${board.bno}" method="PATCH">
          <div class="form-group">
            <label>제목</label>
            <input type="text" name="subject" value="${board.subject}" class="form-control">
          </div>
          <div class="form-group">
            <label>내용</label>
            <textarea name="content" class="form-control" rows="5">${board.content}</textarea>
          </div>
          <button type="submit" class="btn btn-default">수정</button>
        </form:form>
    </div>
</body>
</html>
cs


다 되었으면 저장 후 프로젝트 실행하고 확인하면 수정과 삭제가 잘 되는걸 보실 수 있습니다.




**

기본적인 목록/상세/작성/수정/삭제는 간단하게 끝났네요. 문제는 <form>태그가 필요 없는 경우에도 PATCH, PUT, DELETE 방식으로 요청하려면 <from:form> 태그를 사용해야 하는데.... 보기도 안좋고 만족스럽지가 않아요 ㅋㅋㅋ; 이게 맞는건가 싶고... RestTemplate에 대해 공부를 좀 더 해보고 수정하거나 적용하는 쪽으로 가야겠습니다. 또 얼마나 걸릴지는 모르겠지만^^; 일단 다음 포스팅에서는 파일업로드에 대해 다뤄보도록 하겠습니다.



반응형

댓글

💲 추천 글