저번시간에 AOP 관점차제는
트랜젝션 매니저가 그걸 받아서 매소드 이름 으로 걸러내서
익셉션이 발생하면 롤백하는 정책을 적용함. (익셉션종류는 구분안함)
그래서 서비스메서드 네이밍할떄는 패터닝을 하는게 좋겠따.
겟은 왜잡앗냐
수정을 하기위해서 가져오는건 조회수 안올리니까 읽기만 하는거니까 리드온리
그냥읽는거는 조회수의 DML이 실행되기떄문에 익셉션걸어줘야함
오늘은 서머노트, 댓글
자료를 등록하는건 제외하고 rest 방식의 컨트롤러를 만들고 서브밋형태 다시해보는것
레스트방식의 컨트롤러 (중복처리나 픽쳐업로드) (CRUD) 저번시간에 배웠던거 더 진행....
mvc에서 커맨드 만드는거 중요. 레스트방식에서 더 중요함. json 데이터의 키 이름이 테이블에 부합되도록 나오게되는데
브라우저사용하다보면 json 의 형태가 그렇지않을떄가 많음
화면에서 오는 json데이터를 테이블에 맞게, 반대의 경우도 가능하게 커맨드를 만들어야함
클라이언트가 뭐건간에 웹기반 브라우저를 사용한다는건 우리가 여기서 원활하게 쓸수잇는데이터가 json이라는 거임
화면단에서 처리하는데이터구조는 json이 유력
xml은 잘 사용x 만들기쉽고 읽기쉽다. 이건 데이터가져오는건 유용한데 수정이까다롭다. 스키마구조를찾아가야하므로
우리의 주목적은 CRUD 이기떄문에 U가 원활하지않으면 안됨
암튼 그래서 대부분 json형태를 많이 띄게됨 (매핑이 잘됨 변환이 쉬움)
자바객체도 .으로 접근가능한것처럼 JSON 도 .으로 접근가능
한글알면 일본어 어순같아서 배우기쉬운것처럼
웹단과 자바가 통신을 해야한다면 거의 대부분 JSON임 (접근이 쉬움) 잭슨데이터바인드 쓰면 그대로 바뀌니까
SQL은 데이터를 넣뻇하기위해 만든거지 프로그래밍 언어를 고려하고 나온애가 아님
그래서 SQL과 연결할수있는 컨버터가 필요 (jdbc)
자바에서 sql문을 대신할만한게없기떄문에 sql문을 따로 만들어야한다
자바와 sql 사이의 프레임웍을 ORM 프레임웍. 우리는 마이바티스를 쓰고잇는것... 여기에 Mapper라는 (테이블데이터 컬럼명과 자바 변수명을 매칭할수잇는)게 필요한거임 매퍼적용을 하는데 들어가는게 VO임 매퍼가 컬럼데이터를 VO에 매칭시켜줌
브라우저와 서버사이엔 Spring MVC이라는 모듈을 쓴거고 여기엔 매퍼가 아니라 커맨드객체를 낀거임
즉 레퍼지토리에선 데이터를 넘겨주는게 VO 가 하고, 제이슨서버사이에선 커맨드객체가 그 일을 한다.
이 구조를 그대로 쓰는게 RESTful 방식인거임
일단은 보드를 만들거임
일단 보드쪽 이슈 (서머노트... 이것도 이미지가 레스트방식으로 올라감.. 서브밋할떄는 이미지 이미끝났고 내용만 넘억나는거임) 해결할거임
매퍼랑 VO는 되어잇음
매퍼는 상관없음
어태치와 보드테이블 어짜피 분리되어잇음 (픽쳐가 멤버테이블에 들어가잇는것처럼 안에잇는게아니고) 그래서 안넣으면 그만임
보드매퍼는 보드내용이고 어태치는 어태치매퍼따로잡아서 만들엇으므로 안쓰면 그만인거임
dao를 만들어야함
근데 과거의dao와 차이가없으므로 가져온다.
첨부파일안할거니까 서비스내부의 구현부가 달라질거임
근데지우는게 더 헷갈
그냥 새로만드는게낫겟다
daoimpl 이 문제임 dao는 문제가없음
sql session 받아서 처리하도록
sql session 받는부분도 다 삭제 (파라미터)
*빈그래프는 스프링은 얹엇다는 증거가되니까 발표자료에 써먹으면 좋다
boardServiceImpl()
서비스임플하나씩만들어보자
어태치빠지는거고 세션을 받아와서하는게 아니니까 세션에대한염려를 할필요가 없다는 점
가져와서짜를거냐 짤라서가져올거냐는 sql dao 의 몫임
@Override
public Map<String, Object> getBoardList(SearchCriteria cri) throws SQLException {
// 보드리스트랑 페이지 메이커 두가지를 만들어줘야함
// 리플라이는 리플라이dao가 준비가 된 후에 ...
Map<String, Object> dataMap = new HashMap<String, Object>();
// 현재 page 번호에 맞는 리스트를 perPageNum 개수만큼 가져오기
List<BoardVO> boardList = boardDAO.selectBoardCriteria( cri);
/*// reply count 입력
for (BoardVO board : boardList) {
int replycnt = replyDAO.countReply(session, board.getBno());
board.setReplycnt(replycnt);
}*/
// 전체 board 개수
int totalCount = boardDAO.selectBoardCriteriaTotalCount(cri);
// PageMaker 생성.
PageMaker pageMaker = new PageMaker();
pageMaker.setCri(cri);
pageMaker.setTotalCount(totalCount);
dataMap.put("boardList", boardList);
dataMap.put("pageMaker", pageMaker);
return dataMap;
}
겟보드랑 겟보드모디파이는 뷰카운트 올리냐마냐만 차이임
@Override
public BoardVO getBoard(int bno) throws SQLException {
BoardVO board = boardDAO.selectBoardByBno(bno);
boardDAO.increaseViewCnt( bno);
return board;
}
@Override
public BoardVO getBoardForModify(int bno) throws SQLException {
BoardVO board = boardDAO.selectBoardByBno(bno);
return board;
}
시퀀스를 쿼리문에서 하지말자..
시퀀스에 대한 밸류할당을 쿼리문으로 하게되면 쿼리문에 의존하게됨
넥스트벨류를 꺼내서써야할경우 어려워진다
시퀀스값을 dao에서 꺼내서 선점을 해서 쓰자!!
@Override
public void regist(BoardVO board) throws SQLException {
int bno = boardDAO.selectBoardSeqNext();
board.setBno(bno);
boardDAO.insertBoard(board);
}
@Override
public void modify(BoardVO board) throws SQLException {
boardDAO.updateBoard( board);
}
@Override
public void remove(int bno) throws SQLException {
boardDAO.deleteBoard(bno);
}
컨트롤러
서머노트에 대한 이슈뺴고나면 그냥 제목작성자내용임 . (댓글은 레스트로 따로임)
첨부파일없으니까 멀티파트리졸버에 대한 시알유디 신경슬필요도없음
어노테이션 주기
@Controller
@RequestMapping("/board")
public class BoardController {
@Autowired
private BoardService service;
보드서비스 타입이 하나니까 autowired가 가능
여러개라면 id값을 참조하는 Resource 써야함
@RequestMapping("/main") //넘어오는 url /board/main인데 뷰도 경로가같으므로 생략한거임
public void main() {
// return "/board/main";
}
@RequestMapping("/list")
public ModelAndView list(SearchCriteria cri, ModelAndView mnv) throws Exception{
String url = "board/list";
Map<String , Object> dataMap = service.getBoardList(cri);
mnv.addAllObjects(dataMap);
mnv.setViewName(url);
return mnv;
}
//등록"화면"
@RequestMapping("/registForm")
public String registForm() {
String url = "board/regist";
return url;
}
이 폼보여주면 타이틀 라이터 컨텐트 들어올거고
이게 보드vo에 잇다? 면 그대로 받고 없으면 커맨드 만들면되는거임
보드vo에 있기떄문에 커맨드 안만들어도됨
@RequestMapping("/regist")
public void regist(BoardVO board, HttpServletRequest request, HttpServletResponse response) throws Exception{
service.regist(board);
response.setContentType("text/html;charset=utf-8");
PrintWriter out= response.getWriter();
out.println("<script>");
out.println("window.opener.location.reload(true);window.close();");
out.println("</script>");
out.close();
}
화면이 없어도 컨트롤러에대한 테스트를 할 수 있어야함
rest방식인건 브라우저요청햇을떄 그 json나오는거 확인하면되는데
리다이렉트나 포워드같은건 화면자체가 안나오니까 테스트가 힘들다
컨트롤러만 쭉 만들고, 리다이렉트건 포워드건 테스트해보는걸 해볼거임
일단 컨트롤러마저만들고
bno 인걸 확인하고 파라미터 이름중 bno를 봄 근데 타입이 int임 파라미터에잇는 bno (STring)을 Int 로 바꾸려고함
근데 여기서 int로 바꿀수없는게 뜨면?? 안됨
그래서 반드시 bno는 화면단에서 숫자형태로 바꿀수잇는 형태가 오도록 만들어야함
String from 은 디테일을 보여줄때, 디테일에서 수정으로 넘어갈떄 (이때내용가져오는거랑) 해당 디테일에서 수정하기위해서 넘어간후 수정한 다음 다시 확인위해서 디테일로 넘어올떄 (이때도 내용가져올는거 ) 두분에서 카운트가 먹어버림.. 서버는 디테일 요청으로만 생각하기떄문에 그래서 조회수가 2개가 올라가게됨
그래서 그걸 구분하기위해 String from 에 from modify 줄거임 그런경우에는 겟보드를 하면안되고
뷰카운트를 올리지않ㅇ는 겟보드포모디파이를 할거임
@RequestMapping("/detail")
public ModelAndView detail(int bno, String from, ModelAndView mnv) throws SQLException{
String url = "board/detail";
BoardVO board = null;
if(from!=null && from.equals("modify")) {
board=service.getBoardForModify(bno);
}else {
board=service.getBoard(bno);
}
mnv.addObject("board",board);
mnv.setViewName(url);
return mnv;
}
이제 디테일이 끝나고 수정창으로 넘어갈거임
조회수가 올라가면 안되니까 getboard 가 아니라 포모디파이로 가져와야댐
//수정"화면"
@RequestMapping("/modifyForm")
public ModelAndView modifyForm(int bno, ModelAndView mnv) throws Exception{
String url = "board/modify";
BoardVO board = service.getBoardForModify(bno);
mnv.addObject("board",board);
mnv.setViewName(url);
return mnv;
}
이제 수정을 해야함
수정완료후 디테일로 넘어갈때 주소줄의 파라미터로 from을 줘야함 (리다이렉트이기떄문에 상태에대한 정보를 주소줄로밖에 줄수없음)
@RequestMapping(value="/modify",method=RequestMethod.POST)
public void modifyPost(BoardVO board, HttpServletRequest request, HttpServletResponse response) throws Exception{
service.modify(board);
response.setContentType("text/html;charset=utf-8");
PrintWriter out= response.getWriter();
out.println("<script>");
out.println("window.opener.location.reload();");
out.println("location.href='detail?bno="+board.getBno()+"&from=modify';");
out.println("</script>");
out.close();
}
remove
bno가 넘어오면 보드내용을 지울거임
열려져잇는 화면을 닫고 뒤의화면은 새로고침
@RequestMapping(value="/remove",method=RequestMethod.POST)
public void remove(int bno, HttpServletResponse response) throws Exception{
service.remove(bno);
response.setContentType("text/html;charset=utf-8");
PrintWriter out= response.getWriter();
out.println("<script>");
out.println("window.opener.location.reload();window.close();");
out.println("</script>");
out.close();
}
test
view 없이 서비스 어케 테스트?
디테일 모디파이 리무브 정도만...
디비 먼저 확인해보자
컨트롤러를 테스트할수잇는 pom.xml이 되어잇는가 확인
1. 해당자르:junittest의 버전이 12버전으로 되어잇는지 (11버전이상이어야됨 중요)
2.
mvnrepository.com/artifact/javax.servlet/javax.servlet-api/3.0.1
Maven Repository: javax.servlet » javax.servlet-api » 3.0.1
Professional Java Data: RDBMS, JDBC, SQLJ, OODBMS, JNDI, LDAP, Servlets, JSP, WAP, XML, EJBs, CMP2.0, JDO, Transactions, Performance, Scalability, Object and Data Modeling (2001)by Carl Calvert Bettis, Michael Bogovich, Sean Rhody, Mark Wilcox, Kelly Lin P
mvnrepository.com
mvnrepository.com/artifact/javax.servlet.jsp/javax.servlet.jsp-api/2.3.1
Maven Repository: javax.servlet.jsp » javax.servlet.jsp-api » 2.3.1
Professional Java Data: RDBMS, JDBC, SQLJ, OODBMS, JNDI, LDAP, Servlets, JSP, WAP, XML, EJBs, CMP2.0, JDO, Transactions, Performance, Scalability, Object and Data Modeling (2001)by Carl Calvert Bettis, Michael Bogovich, Sean Rhody, Mark Wilcox, Kelly Lin P
mvnrepository.com
<!-- Servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.1</version>
<scope>provided</scope>
</dependency>
이렇게 "교체"해주기
3. springtest 있는지확인
얘는 톰캣이 작동되서 테스트를 하게하면안됨
이유:톰켓떄문에 문제가 터질수있음(변인통제가 안됨)
그럼 톰켓없이어케
그럴수잇게 톰켓없이도 메서드호출로 컨트롤러 실행할수잇는 mockwebcontext를 넣어놧음
그래서 유알엘을 요청하는게 아니라 해당 유알엘을 메서드조작으로 요청하게됨
원래 기본 스프링 테스트에잇는건 그대로들어감
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:kr/or/ddit/context/root-context.xml")
그래야 서비스를 이용함
서비스가 테스트가 아직 안되엇다면 목서비스를 만들어야하지만 테스트가완료됏다면 그 서비스사용하면됨
우린 서비스 테스트완료라는걸 전제로함
톰캣에 대한 이슈 ) mvc 컨트롤러를 톰켓에 의지하지않고 실행할수잇는놈을 불러오게됨 > 어노케이션 하나로 끝남
@WebAppConfiguration
이걸로 컨트롤러실행가능
대신 주소줄이 아니라 메서드이용
진짜지워지면안되니까 롤백이 필요한곳에 @rollback 줄거임
@Transactional()
근데 이걸 주면 얘는 항상 롤백임
롤백을 줘야하는 부분에 롤백을 줄수잇는게 아니고 무조건 롤백임
그래서 @Rollback 안줘도됨 무조건 롤백임
그럼 롤백을 하지않아야하는 테스트는 어케함?
만약 롤백을 하지않겠따?
@Rollback(false)
@Rollback 을 주고, 값으로 false를 주면 됨
(지난시간에 배운거 정정임)
나는 특정 익셉션이 터졋을떄만 롤백을 하겟다면
@Transactional(rollbackFor="익셉션타입")
이렇게 하면됨
우리는 무조건 롤백이니까
@Autowired
private WebApplicationContext ctx;
원래는 서블릿컨텍스트가 서블릿 읽어오면서 어저구하는거를
얘가 (WebApplicationContext) 대신해줌
그래서 여기는 root-context 만 읽어오면 안되고 다읽어와야함 (서블릿컨텍스트까지)
@ContextConfiguration(locations="classpath:kr/or/ddit/context/root-context.xml")
이부분! 이부분에서
루트 외에 서블릿컨택스트도 읽어야하므로
두개를 읽어야하는거임
여러개를 어케함?
배열로...
@ContextConfiguration(locations= {"classpath:kr/or/ddit/context/root-context.xml",
"classpath:kr/or/ddit/context/servlet-context.xml"})
ㅇ ㅣ거 읽으면서
이거 할당해주는거임
이제 컨트롤러를 호출할수있는 mockmvc를 받자
얘는 우리가 만드는게 아니고 WebApplicationContext 얘가주는거임
마치 url을 호출하는것처럼 파라미터를 이용하면됨
@Before
public void setUp() {
mockMvc = MockMvcBuilders.webAppContextSetup(ctx).build();
}
매번 테스트 진행시마다 저걸 할당받아야하니까 @before 이용
mockmvc빌더(얘도 스프링프레임웤에잇는놈)에 웹컨텍스트셋업을 넣고 빌드를 시키면 mockmvc가 떨어져서
컨트롤러를 우리가 직접 호출할수있게됨
그냥 컨트롤러 빈 호출하면 안됨?
놉 서블렛작동을 안하고 핸들러어댑터도 작동을 안하기떄문에 파라미터도 가져올수없음
이걸 mockmvc는 해주는거임
첫번쨰 테스트는 모디파이
화이트박스테스트이기때문에 유알엘 파라미터등은 미리 알고잇어야함
그걸 메서드형태로 부여하는거임
MockMvcRequestBuilder를 이용할건데 우리가 쓸 메서드들이 static 매서드이기떄문에
클래스명.메서드() 이렇게 적어야하니까
MockMvcRequestBuilder.get(유알엘).param(파라미터들..) 이렇게 주게되는데
너무 길다
그래서
스테틱메서드를 임포트하면
*스태틱메서드를 임포트하자
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
대신에 주의할점
import를 하게되면 (ctrl shift o) 위에 적은게 다 사라짐 주의
이걸 써먹을거임
확인할거
실제로 그런 url호ㅏ면이 넘어오는지, 컨트롤러가 200으로 잘 응답하는지
해당 모델안에 보드라는 어트리뷰트가 잇는지 (그게 뷰리졸버로 넘어가서 리퀘스트에 보드가 심어질테니까)
컨트롤러가 요청시 요청되는것들이랑 컨트롤러가 끝나고 나서 나오는것들을 확인하는거임
@Test
public void testModifyGET()throws Exception{
mockMvc.perform(get("/board/modifyForm").param("bno", "17461"))
.andExpect(status().isOk())
.andExpect(model().attributeExists("board"))
.andExpect(view().name("board/modify"));
}
*405 : 그런 유알엘이잇는데, 그런 메서드방식이없을때
우리가 지금 보드페이지를 보려고하는 유알엘을 던진건데
보드폼을 보여주는건 모디파이폼임
모디파이는 처리엿고, 걔의 메서드방식은 POST밖에없음
근데 우리는 모디파이요청하면서 GET를 줬기떄문에
모디파이POST는잇지만 모디파이GET 은 컨트롤러에없기떄문에 오류난거임
컨트롤러에 가보면
근데 우리는 모디파이폼이아니고 모디파이줌
아예없는건 404, 그런유알엘이 있긴있은데 없는 메서드방식이 나오는게 405임
그래서 modifyForm을 요청햇어야됨
*근데 콘솔에 로그에 다튀어나옴
테스트리소스는 메인리소스안가져오고 테스트리소스에잇는거가져옴
테스트리소스에는 기존의 로그포제이의 형태가그대로임
그래서 저 위에거의 형태를 앙래로 옮겨줘야함
이클립스 버그
톰캣배포폴더까지 가서 덮어씌워야하는...
근데 테스트패키지는 톰캣에 배포도안되는거라 더 버그가심함
그래서 넣뻇계속하거나.. 지웟다가 다시하거나... 계속 시도해야됨
컨트롤러에서 서비스호출햇으니
>>jsp없이도 컨트롤러확인가능
롤백안할거임
이번에는 모디파이에 파라미터마니넣어야함 (제목,작성자,내용)
작성자를 멤버에없는녀석으로 넣으면 터짐(FK라)
isfound는 리다이렉트까지 해야 true라 302뜬다.. isok로 다시수정
모디파이는 리다이렉트안하고잇기떄문에
자바스크립트로 내보내고잇기떄문에...
결과는 디비확인해보는수밖에없음
@Test
@Rollback(false)
public void testModifyPOST()throws Exception{
mockMvc.perform(post("/board/modify").param("bno", "17462")
.param("title", "제목수정")
.param("writer", "mimi")
.param("content", "내용수정"))
.andExpect(status().isOk());
//.andExpect(status().isFound());//302 응답코드 확인 및 Result유무
//.andExpext(redirectedUrl("/board/list"));
}
디테일
@Test
public void testDetail()throws Exception{
mockMvc.perform(get("/board/detail").param("bno", "17462"))
.andExpect(status().isOk())
.andExpect(model().attributeExists("board"))
.andExpect(view().name("board/detail"));
}
리무브
얘는 롤백을 해야함
지워버리면 테스트제한성이없어지기떄문
@Test
public void testRemove()throws Exception{
mockMvc.perform(post("/board/remove").param("bno", "17462"))
.andExpect(status().isFound());
//.andExpect(redirectUrl("/board/list"));
}
isfound>isok 로고쳐야 작동함