Mapper를 작성하여 DataBase와 Spring이 정상적으로 동작하는지 Test 진행
<?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.spring.mvc.board.repository.IBoardMapper">
<!-- DB와 VO객체 간의 변수명이 다를 수 있으므로 해당 변수명 맞춰주는 작업
property는 VO 객체의 변수명 column은 DB 변수명 -->
<resultMap type="com.spring.mvc.board.model.BoardVO" id="BoardMap">
<id property="boardNo" column="board_no" />
<!-- pk라서 id 태그로 작성 -->
<result property="regDate" column="reg_date"/>
<result property="viewCnt" column="view_cnt"/>
<!-- pk가 아닌 데이터는 result 태그로 진행 -->
</resultMap>
<insert id="insert">
<!-- IBoardMapper와 연동하여 DB접근하여 DML 수행 -->
INSERT INTO mvc_board
(board_no, title, content, writer)
VALUES(board_seq.NEXTVAL, #{title}, #{content}, #{writer})
</insert>
<select id="getArticleList" resultMap="BoardMap">
SELECT * FROM mvc_board ORDER BY board_no ASC
</select>
<select id="getArticle" resultMap="BoardMap">
SELECT * FROM mvc_board WHERE board_no = #{num}
</select>
<update id="updateArticle">
UPDATE mvc_board SET title = #{title}, content = #{content} WHERE board_no = #{boardNo}
</update>
<delete id="deleteArticle">
DELETE FROM mvc_board WHERE board_no = #{boardNo}
</delete>
</mapper>
src/test/java에서 test 진행
JUnit 라이브러리를 활용
@RunWith, @ContextConfiguration, @Test Annotation을 통해 test 수행
실제 DB와 연동이 되어 데이터의 조회, 삭제, 삽입, 수정이 가능한지 테스트
test 파일과 InterfaceMapper, Mapper.xml 연동하여 테스트
package com.spring.mvc.board;
import java.util.List;
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 com.spring.mvc.board.model.BoardVO;
import com.spring.mvc.board.repository.IBoardMapper;
@RunWith(SpringJUnit4ClassRunner.class)
// 테스트 환경에서 Mapper 객체 활용을 위해 빈 등록 설정이 있는 xml 파일 로딩
@ContextConfiguration(locations= {"file:src/main/webapp/WEB-INF/spring/root-context.xml"})
// XML 파일 설정을 가져옴
// root-config.xml에 있는 Mapper설정을 가져옴
public class BoardMapperTest {
@Autowired
private IBoardMapper mapper;
// 게시글 등록 단위 테스트
@Test
public void insertTest() {
for (int i = 0; i <= 323; i++) {
BoardVO board = new BoardVO();
board.setTitle("테스트 제목" + i);
board.setWriter("문테스트" + i);
board.setContent("테스트 중입니당" + i);
mapper.insert(board);
// 테스트 값이 실제 DB에 들어가는지 확인
// root-config.xml을 가져왔으므로 될 것 같은데
// 테스트 성공
}
}
//게시글 목록 전체 조회 테스트
//게시물 개수 몇개인지 출력하시고, 게시글 모든 내용을 toString()으로 출력
@Test
public void listTest() {
List<BoardVO> boards = mapper.getArticleList();
// mapper와 연동해서 전체 게시물 가져옴
System.out.println("전체 게시물 개수 : "+ boards.size());
for(BoardVO board : boards) {
System.out.println(board.toString());
// 전체 게시물의 내용 모두 출력
}
}
//게시글 단일 조회 테스트
@Test
public void getBoard() {
int num = 38;
BoardVO board = mapper.getArticle(num);
// 게시물 번호로 특정 게시물 가져옴
System.out.println(num + "번째 게시물 내용 : " +board.getContent());
}
//게시글 수정 테스트
//수정 내용(글 제목, 글 내용)을 입력하고 수정을 진행 (1번글의 제목, 내용)
@Test
public void upBoard() {
BoardVO upBoard = new BoardVO();
upBoard.setBoardNo(1);
upBoard.setTitle("수정 테스트 글 제목");
upBoard.setContent("수정 테스트 글 내용");
mapper.updateArticle(upBoard);
// 특정 게시물 번호로 게시물 수정
BoardVO board = mapper.getArticle(1);
System.out.println(board.getTitle());
System.out.println(board.getContent());
// 수정이 정상적으로 완료되었는지 확인
}
//게시글 삭제 테스트
//3번글을 삭제하세요. 삭제 후 상세보기를 통해 3번글을 가져왔을 때 null이 리턴되는지
//조건문으로 확인해서 결과를 출력
@Test
public void delBoard() {
int num = 3;
mapper.deleteArticle(num);
// 특정 게시물 번호로 게시물 삭제
// delete이후 실제 해당 게시물 번호로 값이 존재하는지 유무 파악
if (mapper.getArticle(num) == null) {
System.out.println("삭제 성공 게시글 없음");
} else {
System.out.println("삭제 실패");
}
}
}
Service 계층을 구현하기 위한 Interface 생성
package com.spring.mvc.board.service;
import java.util.List;
import com.spring.mvc.board.model.BoardVO;
public interface IBoardService {
// 게시글 등록 기능
void insert(BoardVO board);
// 게시글 전체 조회 (페이징 전)
List<BoardVO> getArticleList();
// 게시글 상세 조회 기능
BoardVO getArticle(int boardNo);
// 게시글 수정 기능
void updateArticle(BoardVO board);
// 게시글 삭제 기능
void deleteArticle(int boardNo);
// 게시글 수 조회 기능
}
Interface를 상속받아 Service 클래스 생성
Container에 Bean 등록을 하기 위해 @Service annotation 지정
Service클래스는 Mapper가 필요하므로(dependency) Interface Mapper를 가져오고 @Autowird를 통해 의존성 주입
package com.spring.mvc.board.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.spring.mvc.board.model.BoardVO;
import com.spring.mvc.board.repository.IBoardMapper;
@Service
public class BoardService implements IBoardService {
@Autowired
private IBoardMapper mapper;
@Override
public void insert(BoardVO board) {
mapper.insert(board);
}
@Override
public List<BoardVO> getArticleList() {
return mapper.getArticleList();
// Controller에 값을 보냄
}
@Override
public BoardVO getArticle(int boardNo) {
return mapper.getArticle(boardNo);
}
@Override
public void updateArticle(BoardVO board) {
mapper.updateArticle(board);
}
@Override
public void deleteArticle(int boardNo) {
mapper.deleteArticle(boardNo);
}
}
Container를 Container에 Bean으로 등록을 하기 위해 @Controller annotation 지정
Container는 Service 계층과 연동하여 데이터를 전달해야함으로 Interface Service를 가져오고 @Autowird를 통해 의존성 주입
package com.spring.mvc.board.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import com.spring.mvc.board.model.BoardVO;
import com.spring.mvc.board.service.IBoardService;
@Controller
@RequestMapping("/board")
public class BoardController {
@Autowired
private IBoardService service;
// 게시글 등록
@PostMapping("/write")
public String writeBoard(BoardVO board) {
System.out.println("/board/write : POST");
service.insert(board);
return "redirect:/board/list";
// board/list로 다시 재요청을 보내 리스트를 확인할 수 있도록 함
// 글 등록이후 다시 list로 재요청
}
// 게시글 목록 불러오기
@GetMapping("/list")
public String listBoard(Model model){
System.out.println("/board/list : GET");
model.addAttribute("articles", service.getArticleList());
// model에 articles라는 이름으로 전체 게시물을 담아줌
// model은 지정된 url로 자동 이동
return "board/list";
// 특정 url로 보냄
}
// 게시글 상세보기
@GetMapping("/content")
public String contentBoard(@RequestParam("boardNo") int boardNo, Model model) {
System.out.println("/board/content : GET");
model.addAttribute("article", service.getArticle(boardNo));
return "board/content";
}
// 게시글 수정
@PostMapping("/modify")
public String modifyBoard(BoardVO board) {
System.out.println("/board/modify : POST");
service.updateArticle(board);
return "redirect:/board/content?boardNo=" + board.getBoardNo();
}
// 게시글 삭제
@PostMapping("/delete")
public String deleteBoard(@RequestParam("boardNo") int boardNo, RedirectAttributes ra) {
System.out.println("/board/delete : POST");
service.deleteArticle(boardNo);
ra.addFlashAttribute("msg", "deleteSuccess");
return "redirect:/board/list";
}
}
Controller가 제대로 동작하는지 확인하기 위해 Test 진행
@Before Annotation을 작성하여 가상 구조를 세팅
화면(View)이 없어도 Controller도 테스트 진행할 수 있음
@Before를 사용해서 가상환경을 구축하여 실제 Controller가 구동하는 것과 같이 테스트 진행 가능
package com.spring.mvc.board;
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 com.sun.org.apache.xalan.internal.xsltc.compiler.Parser;
import lombok.extern.log4j.Log4j;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations= {"file:src/main/webapp/WEB-INF/spring/root-context.xml",
"file:src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml"})
// controller는 servlet에 controller에 대한 설정이 있으므로 그것도 가져옴
@WebAppConfiguration
@Log4j
public class BoardControllerTest {
@Autowired
private WebApplicationContext ctx;
/*
WebApplicationContext
- test환경에서 가상의 DispatcherServlet을 사용하기 위한 객체 자동 주입
- 가상 환경을 만들어서 controller가 요청을 받을 수 있도록 해줌
- Spring에서 제공되는 servlet들을 사용할 수 있도록 정보를 저장하는 객체
- @WebAppConfiguration 통해서 가져올 수 있음
*/
// MockMvc는 웹 어플리케이션을 서버에 배포하지 않아도 스프링 MVC 동작을
// 구현할 수 있는 가상의 구조를 만들어 줌
private MockMvc mockMvc;
@Before
public void setUp() {
// 가상 구조 세팅
this.mockMvc = MockMvcBuilders.webAppContextSetup(ctx).build();
// 가상의 mvc 구조 완성
// Spring container에 등록된 모든 bean과 의존성 주입까지 load해서 사용 가능
}
// 위의 3개는 Spring에 있는 모든 Controller를 사용할 때의 Setup
// 만약 하나의 Controller만 사용하고 싶을 때
// @Autowired
// private BoardController controller;
// private MockMvc mockMvc;
// setup메서드에 지정
// this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
// 이렇게 지정하면 하나의 Controller만 테스트 가능
@Test
public void testList() throws Exception {
log.info(
this.mockMvc.perform(MockMvcRequestBuilders.get("/board/list"))
.andReturn()
.getModelAndView()
.getModelMap()
// 가상환경에서도 get 요청을 보낼 수 있고 요청을 받아서 model을 꺼내서 확인하는 작업
// getModelMap()은 map형식으로 확인할 수 있게 해주는 것`
// controller에서 return 했을 때 요기로 와서 연산이 제대로 되는지 확인
// 즉, Controller가 제대로 동작하는지 확인
);
}
@Test
public void testWrite() throws Exception {
String viewPage = this.mockMvc.perform(MockMvcRequestBuilders.post("/board/write")
.param("content", "controller 테스트")
.param("writer", "controller 테스트")
).andReturn().getModelAndView().getViewName();
// 매개변수가 있는 메서드를 테스트할 때에는 .param("이름", "값")을 통해 넣어줄 수 있음
// getViewName()을 통해 어디로 return되는지 확인
log.info(viewPage);
// controller를 통해 실제 요청이 되었고 db에 들어가는 것을 확인
// 이 뜻은 Mapper와 연결되어 실제로 진행된 것
}
//게시글 상세보기 요청 넣어보기.
//컨트롤러에서는 DB에서 가지고 온 글 객체를 model에 담아서 jsp로 이동시킬 것입니다.
//42번글을 보여달라는 요청을 넣으시고, 요청 결과가 들어있는 model을 출력해 보세요.
// /board/content -> get
@Test
public void testContent() throws Exception {
log.info(this.mockMvc.perform(MockMvcRequestBuilders.get("/board/content")
// 해당 요청을 Controller에 보냄
.param("boardNo", "35"))
// 파라미터가 있으므로 파라미터도 추가
.andReturn()
.getModelAndView()
.getModelMap()
);
}
//5번글의 제목과 내용을 수정하는 요청을 보낼 예정입니다.
//전송방식은 post 방식입니다.
//수정 후 이동하는 페이지는 해당 글의 상세보기 페이지입니다.
//컨트롤러가 리턴하는 viewName을 출력해 주세요. ("/board/modify")
@Test
public void testModify() throws Exception {
String viewName = this.mockMvc.perform(MockMvcRequestBuilders.post("/board/modify")
.param("title", "test title 수정 테스트123")
.param("content", "test content 수정 테스트123")
.param("boardNo", "5")
).andReturn().getModelAndView().getViewName();
log.info(viewName);
}
//42번글을 삭제하세요.
//전송 방식은 post방식이고, 이동하는 곳은 목록 요청이 재요청될 것입니다.
//viewName을 출력해 주세요.
@Test
public void testDelete() throws Exception {
String viewname = this.mockMvc.perform(MockMvcRequestBuilders.post("/board/delete")
.param("boardNo", "33"))
.andReturn()
.getModelAndView()
.getViewName();
log.info(viewname);
}
}
servlet-context.xml에서 resources 태그 사용
resources 태그안에 mapping과 location property를 이용하여 경로 설정 가능
<!-- 정적 자원(html, css, js, img ...) 등을 URI 절대 경로로 사용하기 위한 Mapping 처리 -->
<!-- mapping에는 사용자에게 노출되는 경로, location에는 실제 파일 경로 -->
<resources mapping="/resources/**" location="/resources/" />
<resources mapping="/css/**" location="/resources/css/" />
<!-- css/로 된 경로가 있으면 실제로는 /resources/css/에서 찾아라 -->
<resources mapping="/js/**" location="/resources/js/" />
<resources mapping="/img/**" location="/resources/img/" />
<resources mapping="/scss/**" location="/resources/scss/" />
<resources mapping="/vendor/**" location="/resources/vendor/" />
url에 ?boardno = ~~~ 말고 식별자로 가져올 수 있음
기존 방법을 사용하게 되면 url에 boardNo=번호 표시되어 보여짐 (데이터가 사용자에게 보여진다는 것이 좋지 않음)
controller에 해당 요청이 있는 메서드에서 받은 파라미터 앞에 @PathVariable을 작성해서 url 경로에 변수를 포함시켜 파라미터명으로 url을 표현할 수 있음
href="<c:url value='/board/content/${board.boardNo}' />"를 통해 url에 boardNo를 묻혀서 보내면 url이 훨씬 깔끔해짐
// 게시글 상세보기 (?를 써서 파라미터를 보여주는 것이 아니라 특정 구분자로 url에 보여주는 방법)
// 받은 parameter명에 @PathVariable annotation 지정
// @PathVariable은 URL 경로에 변수를 포함시켜 주는 방식
// 만약 파라미터 값에 .이 포함되어 있다면 .뒤의 값은 잘림
// {}안에 변수명 넣고 @PathVariable 괄호 안에 영역을 지목해서 값을 받아옴
// 요청 url뒤에 /{받은 파라미터 명}
@GetMapping("/content/{boardNo}")
public String contentBoard(@PathVariable int boardNo, Model model) {
// 파라미터명과 매개변수 명이 같으면 @RequestParam 안적어도 됨
System.out.println("/board/content : GET");
model.addAttribute("article", service.getArticle(boardNo));
return "board/content";
}