코드로 배우는 스프링 웹 프로젝트를 보고 따라함
조회 구현
화면상의 목록에서 조회를 구현하는 과정은 이전과 동일합니다. 일반적인 게시물 관리의 경우 조회 페이지로 먼저 들어온 후에 수정이나 삭제 작업을 진행하는 것이 보통입니다.
조회 기능 구현은 다음과 같은 순서로 구현됩니다.
- 조회 기능을 위한 BoardDAO의 처리 – 이미 처리된 완료
- BoardService(BoardServiceImpl포함), BoardController의 처리 – 이미 처리된 완료
- 조회 페이지의 작성
- 수정, 삭제 링크 처리
5.1 BoardController의 기능 추가와 뷰 처리
BoardController는 bno를 전달받아서 처리할 수 있는 아래의 기능을 추가해야 합니다.
Org.zerock.controller.BoardController의 일부
@RequestMapping(value="/read", method = RequestMethod.GET) public void read(@RequestParam("bno") int bno, Model model) throws Exception{ model.addAttribute(service.read(bno)); }
|
파라미터는 외부에서 전달될 bno 값을 전달받습니다. 좀 더 명확하게 표현하기 위해서 ‘@RequestParam’을 이용해서 구성합니다. 조회된 결과 게시물을 JSP로 전달해야 하기 때문에 Model 객체를 사용합니다.
@RequestParam 애노테이션은 Servlet에서 request.getParameter()의 효과와 유사합니다.
@RequestParam(“bno”)는 과거 request.getParameter(“bno”)처럼 동작합니다. Servlet의 HttpServletReqest와 다른점은 문자열, 숫자, 날짜 등의 형 변환이 가능하다는 점입니다.
스프링의 Model은 addAtribute() 작업을 할 때 아무런 이름 없이 데이터를 넣으면 자동으로 클래스의 이름을 소문자로 시작해서 사용하게 됩니다. 즉 위의 코드에 들어가는 데이터는 BoardVO 클래스의 객체이므로, ‘boardVO’라는 이름으로 저장되게 됩니다.
5.1.1 조회용 페이지 작성
조회를 처리하는 JSP의 경우 결과 데이터의 변수 이름이 ‘boardVO’라는 점을 주의하면서 작성해야 합니다.
*/WEB-INF/views/board/read.jsp
조회 화면의 경우 나중에 수정이나 삭제 작업에서 사용되기 때문에 반드시 원래 게시물 번호인 bno를 가지고 있어야만 합니다. 가장 먼저 선언된 <form> 태그를 보면 <input type=’hidden’>을 이용해서 bno 값을 처리하는 것을 볼 수 있습니다. readonly 속성을 이용해서 사용자가 내용을 수정할 수 없도록 만들어 줍니다. 마지막에는 조회 화면에서 다른 작업이 가능하도록 버튼을 작성합니다.
5.2 수정, 삭제로의 링크 처리
현재 작성중인 게시물 관리는 게시물을 조회하는 페이지로 이동하고, 수정(Modify)이나 삭제(Remove) 버튼을 이용해서 남은 작업을 처리합니다.
- 게시물 수정은 별도의 수정이 가능한 페이지로 이동한 후에 처리합니다.
- 게시물 삭제는 조회 페이지에서 바로 삭제 처리를 할 수 있도록 합니다.
변경된 화면의 버튼 처리는 jQuery를 사용해서 처리합니다.
위의 코드에서 가장 중요한 핵심은 $(“form[role=’form’]”)으로 선언되어 있는 formObj입니다.
formBoj는 위에 선언된 <form> 태그를 의미하게 됩니다.
<form role=”form” method=”post”>
<input type=’hidden’ name=’bno’ value=”${boardVO.bno}”>
</form>
수정 작업을 하게 된다면 현재의 조회 페이지에서 수정할 수 있는 화면으로 이동해야 합니다. ${“.btn-warning”}의 이벤트 처리는 수정할 수 잇는 페이지로 이동하도록 위의 <form> 태그 속성을 수정하고, 전송하게 됩니다.
삭제의 경우는 $(“.btn-danger”)로 처리됩니다. <form> 태그의 action을 ‘/board/remove’로 되게 처리하고, 전송합니다.
다시 목록으로 가는 작업은 현재 화면의 링크를 ‘/board/listAll’로 처리하고 있습니다.
06 삭제/수정 처리
별도의 수정 페이지를 만드는 작업이 귀찮은 경우도 많지만, 조회 화면과 수정이 가능한 화면은 별도의 화면에서 처리하는 것이 유지보수에는 좀 더 깔끔할 수 있습니다.
삭제 작업은 수정 작업에 비해 처리하기가 좀 더 단순하고, 화면도 필요 없으므로, 삭제를 우선으로 처리합니다.
수정 작업의 경우에는 가능하면 데이터베이스에 최종 수정 시간을 기록으로 남겨두는 것이 좋습니다.
6.1 삭제 처리
//삭제 처리 @RequestMapping(value = "/remove", method = RequestMethod.POST) public String remove(@RequestParam("bno") int bno, RedirectAttributes rttr) throws Exception { service.remove(bno); rttr.addFlashAttribute("msg", "SUCCESS"); return "redirect:/board/listAll"; } |
마지막의 return 부분을 보면 등록 작업과 마찬가지로 리다이렉트 방식으로 리스트 페이지로 이동시켜 버리는 것을 볼 수 있습니다. 삭제 결과는 RedirectAttributes의 addFlashAttribute()를 이용해서 처리하고 있습니다.
addFlashAttribute()로 저장되었으므로, 동일한 URI를 ‘새로 고침’하면 경고창은 뜨지 않습니다.
6.2 수정 처리
경우에 따라서 다를 수 있지만, 수정할 때 수정할 수 있는 별도의 화면을 제공하는 형태로 제작합니다.
조회 화면(read.jsp)에서 수정할 수 있는 화면으로의 이동은 jQuery를 통해서 처리되었습니다.
$(document).ready(function() { var formObj = $("form[role='form']"); console.log(formObj);
$(".btn-warning").on("click", function() { formObj.attr("action", "/board/modify"); formObj.attr("method", "get"); formObj.submit(); }); |
위의 코드에서 가장 중요한 핵심은 $(“form[role=’form’]”)으로 선언되어 있는 formObj입니다. formObj는 위의 선언된 <form> 태그를 의미하게 됩니다.
<form role=”form” method=”post”>
<input type=’hidden’ name=’bno’ value=”${boardVO.bno}”>
</form>
수정 작업을 하게 된다면 현재의 조회 페이지에서 수정할 수 있는 화면으로 이동해야 합니다. $(“.btn-warning”)의 이벤트 처리는 수정할 수 있는 페이지로 이동하도록 위의 <form> 태그 속성을 수정하고, 전송하게 됩니다.
삭제의 경우는 $(“.btn-danger”)로 처리됩니다. <form> 태그의 action을 ‘/board/remove’로 되게 처리하고, 전송합니다.
다시 목록으로 가는 작업은 현재 화면의 링크를 ‘/board/listAll’로 처리하고 있습니다.
06 삭제/수정 처리
별도의 수정 페이지를 만드는 작업이 귀찮은 경우도 많지만, 조회 화면과 수정이 가능한 화면은 별도의 화면에서 처리하는 것이 유지보수에는 좀 더 깔끔할 수 있습니다.
삭제 작업은 수정 작업에 비해 처리하기가 좀 더 단순하고, 화면도 필요 없으므로, 삭제를 우선으로 처리합니다.
수정 작업의 경우에는 가능하면 데이터베이스에 최종 수정 시간을 기록으로 남겨두는 것이 좋습니다.
6.1 삭제 처리
삭제에 대한 처리는 POST 방식으로 조회 화면에서 처리합니다. 삭제는 등록 기능과 유사한 부분이 많은데, 삭제 후 페이지의 이동을 제대로 처리하지 않으면 브라우저에서 ‘새로고침을’을 통해 계속해서 동일한 데이터가 재전송될 수 있는 문제가 있습니다.
BoardController에 remove 메소드를 작성합니다.
//삭제 처리 @RequestMapping(value = "/remove", method = RequestMethod.POST) public String remove(@RequestParam("bno") int bno, RedirectAttributes rttr) throws Exception { service.remove(bno); rttr.addFlashAttribute("msg", "SUCCESS"); return "redirect:/board/listAll"; } |
마지막으로 return 부분을 보면 등록 작업과 마찬가지로 리다이렉트 방식으로 리스트 페이지로 이동시켜 버리는 것을 볼 수 있습니다. 삭제 결과는 RedirectAttributes의 addFlashAttribute()를 이용해서 처리하고 있습니다.
실행해 보면 삭제 작업 후 다시 리스트 화면으로 이동하는 것을 볼 수 있습니다.
addFalshAttribute()로 저장되었으므로, 동일한 URI를 ‘새로 고침’하면 경고창은 뜨지 않습니다.
6.2 수정 처리
경우에 따라서 다를 수 있지만, 이번 장에서는 수정 역시 수정할 수 있는 별도의 화면을 제공하는 형태로 제작합니다.
조회 화면(read.jsp)에서 수정할 수 있는 화면으로의 이동은 jQuery를 통해서 처리되었습니다.
$(".btn-warning").on("click", function() { formObj.attr("action", "/board/modify"); formObj.attr("method", "get"); formObj.submit(); }); |
컨트롤러에서는 ‘/board/modify’에 맞는 메소드를 작업합니다.
Org.zerock.controller.BoardController의 일부
// 수정 @RequestMapping(value = "/modify", method = RequestMethod.GET) public void modifyGET(int bno, Model mode) throws Exception { model.addAttribute(service.read(bno)); }
@RequestMapping(value = "/modify", method = RequestMethod.POST) public String modifyPOST(BoardVO board, RedirectAttributes rttr) throws Exception { logger.info("mod post.......");
service.modify(board); rttr.addFlashAttribute("msg", "SUCCESS"); return "redirect:/board/listAll"; } |
ModifyGET()은 GET 방식으로 조회 페이지로 이동하게 합니다. 이때는 다시 원래의 게시물 데이터를 읽어와서 Model에 넣어서 전달합니다.
modifyPost()는 POST 방식으로 실제 수정 작업을 처리합니다. 이때 리턴 타입은 등록이나 삭제와 동일하게 처리하는 것을 볼 수 있습니다.
수정용 페이지는 modify.jsp 페이지가 됩니다만, BoardController에서의 코드와 jsp 코드는 조회(/board/read) 작업의 것과 큰 차이가 없습니다.
l Modify.jsp
/WEB-INF/views/board/modify.jsp
<%@ page language="java" contentType="text/html; charset=EUC-KR" pageEncoding="EUC-KR"%> <!DOCTYPE html> <html> <head> <meta charset="EUC-KR"> <title>Insert title here</title> </head> <body> <form role="form" method="post"> <div class="box-body"> <div class="form-group"> <label for="exampleInputEmail1">BNO</label> <input type="name='bno' class=" form-control" value="${boardVO.bno}" readonly="readonly"> </div> <div class="form-group"> <label for="exampleInputEmail1">Title</label> <input type="text" name='title' class="form-control" value="${boardVO.title}"> </div> <div class="form-group"> <label for="exampleInputEmail1">Writer</label><input type="text" name="writer" class="form-control" value="${boardVO.writer}"> </div> </div> <!-- /.box-body --> </form>
<div class="box-footer"> <button type="submit" class="btn btn-primary">SAVE</button> <button type="submit" class="btn btn-warning">CANCEL</button> </div>
<script> $(document).read(function() { var formObj = $("form[role='form']"); console.log(formObj);
$("btn-warning").on("click", function() { self.location = "/board/listAll"; });
$(".btn-primary").on("click", function() { formObj.submit(); });
}); </script> </body> </html> |
Modify.jsp 에는 <form> 태그가 전체의 모든 내용을 감싸도록 선언되어 있습니다. 이것은 수정 작업에 필요한 모든 데이터를 <form> 태그로 묶어서 전송해야 하기 때문입니다.
$(“.btn-warning”)는 사용자가 화면에서 ‘CANCEL’ 버튼을 누르면 다시 목록 페이지로 이동하게 됩니다.
‘save’ 버튼을 누르게 되면 post 방식으로 데이터를 수집하고, 이를 서비스 객체에 전달해서 처리하게 됩니다.
결과의 처리는 등록 작업이나 삭제 작업과 동일하게 ‘/board/listAll’을 호출하는 형태로 처리하게 됩니다.
07 예외 처리
7.1 예외 처리에 대한 팁
Spring MVC를 사용할 때 Controller쪽에서 Exception을 처리하기 위해서는 다음과 같은 방식들을 이용합니다.
- @ExceptionHandler 애노테이션을 이용한 처리
- @ControllerAdvice를 이용한 처리
- @ResponseStatus를 이용한 Http 상태 코드 처리
이 중에서 가장 범용으로 사용할 수 있는 방법은 ‘@ControllerAdvice’를 이용한 처리방식입니다. 이 방식은 공통의 Exception처리 전용 객체를 사용하는 방법입니다.
스프링 MVC에서 제공하는 @ControllerAdvice는 호출되는 메소드에서 발생된 Exception을 모두 처리하는 역할을 합니다.
- 클래스에 @ControllerAdvice라는 애노테이션 처리
- 각 메소드에 @ExceptionHandler를 이용해서 적절한 타입의 Exception을 처리
7.1.1 Exception을 화면으로 전달하기
발생한 Exception을 화면으로 전달해 주면 개발 시 보다 유용하게 사용할 수 있습니다.
08 페이징 처리
영속 계층, 비즈니스 계층
페이징 처리의 핵심은 1) 사용자에게 필요한 만큼의 데이터를 전송해야 하고, 2) 서버에서 최대한 빠르게 결과를 만들어 내야 한다는 점입니다.
페이징 처리를 공부할 때는 다음과 같은 단계를 통해서 접근하면 편리합니다.
- Uri의 문자열을 조절해서 원하는 페이지의 데이터가 출력되도록 하는 단계
- 목록 페이지 하단에 페이지 번호를 보여주고, 클릭하면 페이지가 이동하는 단계
- 목록 페이지에서 조회나 수정 작업을 한 후에 다시 원래의 목록 페이지로 이동할 수 있게 처리하는 단계
1단계의 처리는 페이지 번호 등을 파라미터로 전달하고, 이를 이용해서 sql에 적절한 데이터를 뽑아서 보여주는 단계입니다. 이 단계에서 중요한 것은 오직 페이지 번호에 해당하는 데이터를 출력하는 것입니다.
2단계는 목록 페이지의 하단에 페이지 번호를 보여주고, 이를 클릭하면 원하는 페이지로 이동하는 단계입니다. 이때는 1) 원하는 페이지의 데이터를 구하는 작업과 2) 화면 하단에 페이지를 출력해 주는 작업이 필요합니다. 이 단계는 이전이나 다음에 대한 처리, 시작 페이지 번호와 끝 페이지 번호에 대한 계산도 같이 처리됩니다.
3단계는 페이징 처리에서 가장 어려운 목록 페이지에서 클릭을 통해 조회나 수정 작업등을 하고 다시 원래의 목록 페이지를 볼 수 있게 하는 기능입니다. 예를 들어 3페이지에서 어떤 게시물을 조회하고, 다시 목록 가기 버튼을 클릭해서 기존의 3페이지를 다시 볼 수 있게 하는 기능입니다. 이 기능이 구현되지 않으면 사용자는 매번 페이지를 이동하고, 조회한 후에 다시 1페이지에서부터 데이터를 찾아야 하기 때문에 반드시 필요한 기능입니다.
8.1 페이징 처리 방식
페이징 처리의 핵심은 모든 작업에 필요한 리스트를 보는 데 필요한 정보가 같이 유지돼야만 한다는 점입니다. 웹 페이지는 이런 작업을 복잡하게 계산해서 처리합니다.
- <a> 태그의 href 속성을 이용해서 직접 이동할 URI를 지정하는 방식
- <form> 태그를 이용해서 링크를 클릭하면 여러 정보를 전달하는 방식
<a> 태그를 이용하는 경우, 최대의 장점은 검색엔진에 노출이 쉬워진다는 점입니다. 대부분의 검색엔진은 웹 페이지의 html을 파싱해서 해당 게시물 관련 정보를 처리합니다. 모든 게시물에 위와 같이 링크가 처리되면 한 번에 모든 연결정보를 파악할 수 있다는 장점이 생깁니다.
Part 2에서는 이러한 처리를 보다 간결하게 할 수 있도록 링크에는 최소한의 정보를 이용하고, 빠르게 개발할 수 있는 <form> 태그를 이용해서 처리할 것입니다. <form> 태그를 이용하는 방식은 필요한 정보를 클릭하는 경우 <form> 태그 내에 필요한 정보를 담아서 처리하는 방식입니다.
<a> 태그로 모든 링크에 대한 정보를 처리하려면 많은 양의 반복적인 링크 정보가 생성되지만, <form> 태그를 이용하는 경우, 필요한 정보는 <form> 태그 내에서 작성되어서 사용되기 때문에 구현에 있어서는 보다 편리합니다.
8.1.1 페이징 처리의 원칙
페이징 처리에는 반드시 다음과 같은 원칙이 지켜져야만 합니다.
- 페이징 처리는 반드시 get 방식만을 이용해서 처리합니다.
- 페이지는 다른 사람에게 url로 전달하는 경우가 많기 때문에, 반드시 get 방식으로만 처리됩니다.
- 페이징 처리가 되면 조회 화면에서 반드시 ‘목록 가기’가 필요합니다.
- 목록 페이지에서 3페이지를 보다가 특정 게시물을 보았다면, 다시 목록 가기 버튼을 눌러서 다시 목록에서 3페이지로 이동하는 기능이 구현되어야 합니다.
- 페이징 처리에는 반드시 필요한 페이지 번호만을 제공합니다.
- 예컨대, 한 페이지에 10개씩 데이터를 출력하는 경우라면, 전체 데이터가 32건이 있을 때에는 4페이지까지만 화면에 출력돼야 합니다. 만일 더 많은 데이터가 있을 때에는 적당한 수의 페이지 번호를 출력하고, 뒤로 가는 화살표 등을 이용해서 보여줘야 합니다.
8.2 페이징 처리 개발에 필요한 지식
- 페이징 처리를 위한 sql
- 데이터 개수 파악을 위한 sql
- 자바 스크립트 혹은 <a> 태그를 통한 이벤트 처리
페이징 처리의 sql은 현재 사용하는 데이터베이스에 따라 처리하는 sql이 달라질 수 있습니다. Sql을 제외하면 가장 복잡한 부분은 계산을 통해서 원하는 페이지의 번호나 링크를 처리하는 작업입니다.
아퍼 말했듯이 페이징 처리 개발의 순서는 다음과 같습니다.
- 단순히 페이지 데이터가 화면에 출력하는 작업
- 화면 하단에 페이지 번호가 표시되고, 이를 클릭할 경우 제대로 이동하는 작업
- 조회 페이지에서 목록 가기를 선택하면 보던 페이지 정보를 유지한 채로 이동하는 작업
8.2.1 mysql의 limit를 이용한 페이지 출력 sql
우선 게시물 관리의 특성상 게시물은 작성된 시간의 역순으로 출력됩니다. 앞 장에서 만든 테이블의 경우 게시물 번호인 bno 칼럼이 자동으로 다음 번호를 생성하는 auto_increment로 되어 있으므로, 시간의 역순 대신에 bno의 역순으로 되는 sql 문을 그대로 활용할 수 있습니다.
우선 게시물 고나리의 특직상 게시물은 작성된 시간의 역순으로 출력됩니다. 앞 장에서 만든 테이블의 경우 게시물 번호인 bno 칼럼이 자동으로 다음 번호를 생성하는 auto_increment로 되어 있으므로, 시간의 역순 대신에 bno의 역순으로 되는 sql문을 그대로 활용할 수 있습니다.
Mysql의 경우 전체 데이터 중에서 일부분의 데이터만 출력하고 싶은 경우에는 limit 구문을 이용해서 처리합니다.
8.2.1.1 실습을 위해 충분한 양의 데이터 넣기
따라서 sql 문을 자가 복사(self copy)하는 형태로 데이터를 많이 넣어주도록 합니다.
Insert into tbl_board (title, content, writer)(select title, content, writer from tbl_board);
코드로 배우는 스프링 웹 프로젝트를 보고 따라함
'JAVA > spring' 카테고리의 다른 글
paging processing(페이징 처리) - 2 (0) | 2019.08.09 |
---|---|
paging processing(페이징 처리) - 1 (0) | 2019.08.08 |
spring redirection (0) | 2019.08.07 |
spring redirect (4) | 2019.07.04 |
register(등록), success(등록 성공) (0) | 2019.07.04 |
댓글