[spring] 프레젠테이션(웹) 계층의 CRUD 구현
업데이트:
Controller의 작성
스프링 MVC의 Controller는 하나의 클래스 내에서 여러 메서드를 작성하고, @RequestMapping 등을 이용해서 URL를 분기하는 구조로 작성할 수 있다.
Tomcat으로 실행하고 웹 화면을 만들어서 결과를 확인하는 방식은 시간이 오래 걸리고, 테스트를 자동화 하기에 어려움이 많다. 이 단계에서는 WAS를 실행하지 않고 Controller를 테스트 할 수 있는 방법을 테스트 해야한다.
BoardController의 분석
작성하기 전에 원하는 기능을 호출하는 방식에 대해 테이블을 만들어 코드를 작성하는 것이 좋다.
Task | URL | method | Parameter | From | URL 이동 |
---|---|---|---|---|---|
전체 목록 | /board/list | GET | |||
등록 처리 | /board/register | POST | 모든 항목 | 입력화면 필요 | 이동 |
조회 | /board/read | GET | bno=123 | ||
삭제 처리 | /board/remove | POST | bno | 입력화면 필요 | 이동 |
수정 처리 | /board/modify | POST | 모든 항목 | 입력화면 필요 | 이동 |
BoardController의 작성
- BoardController.java 클래스 생성
package org.zerock.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 {
}
- @Controller : 스프링이 빈으로 인식할 수 있도록 하는 어노테이션
- @RequestMapping : ‘/board’로 시작하는 모든 처리를 해당 클래스에서 처리하도록 지정
목록에 대한 처리와 테스트
전체 목록을 가져오는 처리와 BoardService 타입의 객체와 같이 연동해야 하므로 의존성에 대한 처리도 같이 진행한다.
// BoardController 클래스
package org.zerock.controller;
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 org.zerock.service.BoardService;
import lombok.AllArgsConstructor;
import lombok.extern.log4j.Log4j;
@Controller
@Log4j
@RequestMapping("/board/*")
@AllArgsConstructor
public class BoardController {
private BoardService service;
@GetMapping("/list")
public void list(Model model) {
log.info("list");
model.addAttribute("list", service.getList());
}
}
BoardController는 BoardService에 의존적이므로 @AllArgsConstructor
를 이용해서 생성자를 만들고 자동으로 주입하도록 한다.
list()는 나중에 게시물의 목록을 전달해야 하므로 Model
을 파라미터로 지정하고, 이를 통해서 BoardServiceImpl 객체의 getList() 결과를 담아 전달한다.
테스트
- BoardControllerTest 클래스 생성
웹 서버를 실행시켜 테스트 하지 않기 위해 약간의 추가적인 코드가 더있긴 하지만, 서버를 실행하고 화면에 입력하고, 오류를 수정하는 단계를 줄여줄 수 있기 때문에 Controller를 테스트할 때는 한 번쯤 고려해볼만한 방식이다.
// BoardControllerTests
package org.zerock.controller;
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 org.zerock.domain.BoardVO;
import lombok.Setter;
import lombok.extern.log4j.Log4j;
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration //Test for Controller
@ContextConfiguration({
"file:src/main/webapp/WEB-INF/spring/root-context.xml",
"file:src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml"
})
//Java Config
//@ContextConfiguration(classes={org.zerock.config.RootConfig.class, org.zerock.config.ServletConfig.class})
@Log4j
public class BoardControllerTests {
@Setter(onMethod_ = {@Autowired})
private WebApplicationContext ctx;
private MockMvc mockMvc;
@Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(ctx).build();
}
@Test
public void testList() throws Exception {
log.info(
mockMvc.perform(MockMvcRequestBuilders.get("/board/list"))
.andReturn()
.getModelAndView()
.getModelMap());
}
}
등록 처리와 테스트
// BoardController 클래스 추가
@PostMapping("/register")
public String register(BoardVO board, RedirectAttributes rttr) {
log.info("register : " + board);
service.register(board);
rttr.addFlashAttribute("result", board.getBno());
return "redirect:/board/list";
}
RedirectAttributes는 추가적으로 새롭게 등록된 게시물의 번호를 같이 전달하기 위해 사용한다. 리턴 시에는 redirect:
접두어를 사용하는데, 이를 이용하면 스프링 MVC가 내부적으로 response.sendRedirect()를 처리해준다.
// 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);
}
테스트할 때 param()
을 이용해서 전달해야 하는 파라미터들을 지정할 수 있다. 위의 방식으로 코드를 작성하면 매번 입력할 필요가 없기 때문에 오류가 발생하거나 수정하는 경우 반복적인 테스트가 수월해진다.
INFO : jdbc.audit - 1. Connection.prepareStatement(INSERT INTO tbl_board (bno, title, content, writer)
VALUES (seq_board.NEXTVAL, ?, ?, ?)) returned net.sf.log4jdbc.sql.jdbcapi.PreparedStatementSpy@27cbfddf
INFO : jdbc.audit - 1. PreparedStatement.setString(1, "테스트 새글 제목") returned
INFO : jdbc.audit - 1. PreparedStatement.setString(2, "테스트 새글 내용") returned
INFO : jdbc.audit - 1. PreparedStatement.setString(3, "user00") returned
INFO : jdbc.sqlonly - INSERT INTO tbl_board (bno, title, content, writer) VALUES (seq_board.NEXTVAL, '테스트 새글 제목',
'테스트 새글 내용', 'user00')
INFO : jdbc.sqltiming - INSERT INTO tbl_board (bno, title, content, writer) VALUES (seq_board.NEXTVAL, '테스트 새글 제목',
'테스트 새글 내용', 'user00')
조회 처리와 테스트
// BoardController.java
@GetMapping("/get")
public void get(@RequestParam("bno") Long bno, Model model) {
log.info("/get");
model.addAttribute("board", service.get(bno));
}
BoardController의 get() 메서드에는 bno 값을 좀 더 명시적으로 처리하는 @RequestParam
을 이용해서 지정한다. 또한 화면 쪽으로 해당 번호의 게시물을 전달해야 하므로 Model을 파라미터로 지정한다.
// BoardControllerTests.java
@Test
public void testGet() throws Exception{
log.info(mockMvc.perform(MockMvcRequestBuilders
.get("/board/get")
.param("bno", "20"))
.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.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);
}
MockMvc를 이용해서 파라미터를 전달할 때에는 문자열로만 처리해야 한다. 테스트 전에 게시물의 번호가 존재하는지 확인하고 테스트를 실행한다. 로그의 일부는 아래와 같이 SQL이 실행되는 것을 확인할 수 있다.
INFO : jdbc.audit - 1. PreparedStatement.setString(1, "수정된 테스트 새글 제목") returned
INFO : jdbc.audit - 1. PreparedStatement.setString(2, "수정된 테스트 새글 내용") returned
INFO : jdbc.audit - 1. PreparedStatement.setString(3, "user00") returned
INFO : jdbc.audit - 1. PreparedStatement.setLong(4, 1) returned
INFO : jdbc.sqlonly - UPDATE tbl_board SET title = '수정된 테스트 새글 제목', content = '수정된 테스트 새글 내용', writer = 'user00',
updateDate = sysdate WHERE bno = 1
INFO : jdbc.sqltiming - UPDATE tbl_board SET title = '수정된 테스트 새글 제목', content = '수정된 테스트 새글 내용', writer = 'user00',
updateDate = sysdate WHERE bno = 1
- 참고 : 코드로 배우는 스프링 웹 프로젝트
댓글남기기