[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 클래스 생성
    컨트롤러1
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 클래스 생성
    컨트롤러2


웹 서버를 실행시켜 테스트 하지 않기 위해 약간의 추가적인 코드가 더있긴 하지만, 서버를 실행하고 화면에 입력하고, 오류를 수정하는 단계를 줄여줄 수 있기 때문에 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());
	}
}

컨트롤러3






등록 처리와 테스트

// 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());
}

컨트롤러4






수정 처리와 테스트

수정 작업은 등록과 유사하다. 변경된 내용을 수집해서 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 







  • 참고 : 코드로 배우는 스프링 웹 프로젝트

태그:

카테고리:

업데이트:

댓글남기기