스프링 - Part 3 - 기본적인 웹 게시물 관리 04 (프레젠테이션(웹) 계층의 CRUD 구현)
프레젠테이션(웹) 계층의 CRUD 구현
Controller의 작성
스프링 MVC의 Controller는 하나의 클래스 내에서 여러 메서드를 작성하고 @RequestMapping 등을 이용해서 URL을 분기하는 구조로 작성할 수 있기 때문에 하나의 클래스에서 필요한 만큼 메서드의 분기를 이용하는 구조로 작성한다.
BoardController의 분석
작성하기 전에 반드시 현재 원하는 기능을 호출하는 방식에 대해 테이블로 정리한 후 코드를 작성하는 것이 좋다.
테이블에서 From 항목은 해당 URL을 호출하기 위해서 별도의 입력화면이 필요하다는 것을 의미한다.
BoardController의 작성
BoardController는 org.codehows.controller 패키지에 선언하고 URL 분석된 내용들을 반영하는 메서드를 설계한다.
// BoardController.java
package org.codehows.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import lombok.extern.log4j.Log4j;
@Controller
@Log4j
@RequestMapping("/board/*")
public class BoardController {
}
BoardController는 @Controller 어노테이션을 추가해서 스프링의 빈으로 인식할 수 있게 하고 @RequestMapping을 통해서 '/board'로 시작하는 모든 처리를 BoardController가 하도록 지정한다.
목록에 대한 처리와 테스트
BoardController에서 전체 목록을 가져오는 처리를 먼저 작성한다.
BoardController는 BoardService 타입의 객체와 같이 연동해야 하므로 의존성에 대한 처리도 같이 진행한다.
// BoardController.java
package org.codehows.controller;
import org.codehows.service.BoardService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import lombok.extern.log4j.Log4j;
@Controller
@Log4j
@RequestMapping("/board/*")
public class BoardController {
private BoardService service;
@GetMapping("/list")
public void list(Model model) {
log.info("list");
model.addAttribute("list", service.getList());
}
}
src/test/java에 org.codehows.controller 패키지에 BoardControllerTests 클래스를 선언한다.
BoardControllerTests 클래스
// BoardControllerTests.java
package org.codehows.controller;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.log;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import lombok.Setter;
import lombok.extern.log4j.Log4j;
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration({
"file:src/main/webapp/WEB-INF/spring/root-context.xml",
"file:src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml"})
@Log4j
public class BoardControllerTests {
@Setter(onMethod_ = {@Autowired})
private WebApplicationContext ctx;
private MockMvc mockMvc;
@Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(ctx).bulid();
}
@Test
public void testList() throws Exception {
log.info(
mockMvc.perform(MockMvcRequestBuilders.get("/board/list"))
.andReturn()
.getModelAndView()
.getModelMap());
}
}
테스트 클래스의 선언부에는 @WebAppConfiguration 어노테이션을 적용한다.
@WebAppConfiguration 은 Servlet의 ServletContext를 이용하기 위해서인데, 스프링에서는 WebApplicationContext라는 존재를 이용하기 위해서이다. @Before가 적용된 메서드는 모든 테스트 전에 매번 실행되는 메서드가 된다.
등록 처리와 테스트
BoardController에 POST 방식으로 처리되는 register()를 작성하면 아래와 같다.
// BoardController.java
... 생략 ...
// 추가
@PostMapping("/register")
public String register(BoardVO board, RedirectAttributes rttr) {
log.info("register: " + board);
rttr.addFlashAttribute("result", board.getBno());
return "redirect:/board/list";
}
}
BoardControllerTests 클래스
// BoardControllerTests.java
... 생략 ...
// 추가
@Test
public void testRegister()throws Exception {
String resultPage = mockMvc.perform(MockMvcRequestBuilders.post("/board/register")
.param("title", "테스트 새글 제목")
.param("content", "테스트 새글 내용")
.param("writer", "user00")
).andReturn().getModelAndView().getViewName();
log.info(resultPage);
}
}
조회 처리와 테스트
등록 처리와 유사하게 조회 처리도 BoardController를 이용해서 처리할 수 있다. 특별한 경우가 아니라면 조회는 GET 방식으로 처리하므로, @GetMapping을 이용한다.
BoardController 클래스
// BoardController.java
... 생략 ...
// 추가
@GetMapping("/get")
public void get(@RequestParam("bno") Long bno, Model model) {
log.info("/get");
model.addAttribute("board", service.get(bno));
}
BoardControllerTests 클래스
// BoardControllerTests.java
... 생략 ...
@Test
public void testGet() throws Exception {
log.info(mockMvc.perform(MockMvcRequestBuilders
.get("/board/get")
.param("bno", "2"))
.andReturn()
.getModelAndView().getModelMap());
}
}
수정 처리와 테스트
수정 작업은 등록과 유사하다. 변경된 내용을 수집해서 BoardVO 파라미터로 처리하고, BoardService를 호출한다. 수정작업을 시작하는 화면의 경우에는 GET 방식으로 접근하지만 실제 작업은 POST 방식으로 동작하므로 @PostMapping을 이용해서 처리한다.
// BoardController.java
... 생략 ...
@PostMapping("/modify")
public String modify(BoardVO board, RedirectAttributes rttr) {
log.info("modify: " + board);
if(service.modify(board)) {
rttr.addFlashAttribute("result", "success");
}
return "redirect:/board/list";
}
}
BoardControllerTests 클래스
// BoardControllerTests.java
... 생략 ...
@Test
public void testModify() throws Exception {
String resultPage = mockMvc
.perform(MockMvcRequestBuilders.post("/board/modify")
.param("bno", "1")
.param("title", "수정된 테스트 새글 제목")
.param("content", "수정된 테스트 새글 내용")
.param("writer", "user00"))
.andReturn().getModelAndView().getViewName();
log.info(resultPage);
}
삭제 처리와 테스트
삭제 처리도 조회와 유사하게 BoardController와 테스트 코드를 작성한다. 삭제는 반드시 POST 방식으로만 처리한다.
BoardController 클래스
// BoardController.java
... 생략 ...
@PostMapping("/remove")
public String remove(@RequestParam("bno") Long bno, RedirectAttributes rttr) {
log.info("remove..." + bno);
if(service.remove(bno)) {
rttr.addFlashAttribute("result", "success");
}
return "redirect:/board/list";
}
BoardControllerTests 클래스
// BoardControllerTests.java
... 생략 ...
@Test
public void testRemove() throws Exception {
// 삭제전 데이터베이스에 게시물 번호 확인 할 것
String resultPage = mockMvc.perform(MockMvcRequestBuilders.post("/board/remove")
.param("bno", "25")
).andReturn().getModelAndView().getViewName();
log.info(resultPage);
}