102일차 Spring CRUD게시판-표현 계층

쿠우·2022년 8월 30일
0

표현계층해서 해야하는 일은 Request Mapping Table 설계와 이에 맞게 Controller , JSP를 구현해야한다

  • 표현계층에 대해서는 대중소로 나눠서 매핑 테이블을 구성한다.
    이를 기반으로 패키지를 구분하고 생성한다.
  • GET은 조회할 때
  • POST는 처리할 때

Spring Element 는 모든 빈들에 대한 정보를 담고 있다.

안전성을 위해 항상 먼저 Test를 하고 기능검증 후 코드작성

- Controller를 Test하기 위한 방법 ★★★

  • test하는데 필요한 주입객체는 무엇이 있는지(WebApplicationContext 클래스)
  • @WebAppConfiguration << 이게 controller를 테스트 할 때 핵심인 어노테이션
  • 순서대로 한번 쭉 읽으면서 전체적 흐름 파악
@Log4j2
@NoArgsConstructor

// Spring Bean Container & DI 수행시키는 어노테이션 
@ExtendWith(SpringExtension.class)
@ContextConfiguration(locations = {
		"file:src/main/webapp/WEB-INF/**/*-context.xml"
})
// Spring MVC Framework 구동시키는 어노테이션 
@WebAppConfiguration

@TestInstance(Lifecycle.PER_CLASS)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class BoardControllerTests {

	//WAS 구동없이 테스트 하려는 대상이 BoardController 이니, 
	// 이 BoardController 를 주입받아, 메소드 호출로 테스트해야지 --> 틀린생각
//	@Setter(onMethod_= {@Autowired})
//	private BoardController controller;
	
	
	// Spring Beans Container(type:WebApplicationContext) 자체를 주입한다.
	@Setter(onMethod_= {@Autowired})
	private WebApplicationContext ctx;
	

	
	@BeforeAll
	void beforeAll() {
		log.trace("beforeAll() invoked.");
		
		assertNotNull(this.ctx);
		
		log.info("\t+ ctx:{}", this.ctx);
	}// beforeAll
	
	
	@Test
	@Order(1)
	@DisplayName("1. BoardController.list")
	@Timeout(value=3, unit =TimeUnit.SECONDS)
	void testList() throws Exception {
		log.trace("testList() invoked.");
		
		//1.
		//WAS 구동없이 , Controller 의  Handler method를 테스트하려면 , Spring-test 의존성에 포함된
		// MockMVC 객체가 필요하다. 이 MockMVC 객체를 생성하기 위해서 ,
		//바로 위에서 주입받은 Spring Beans Container가 필요하다 .
		
		//2. 아래의 MockMvc의 메소드들은 , fluent-api , method -chaining 기법으로 사용하게된다. 
		
		//Step.1  MockMvcBuilder 객체를 어는다. 
//		MockMvcBuilder mockMvcBuilder = MockMvcBuilders.webAppContextSetup(ctx);
		
		//Step.2 MockMvc 객체를 생성한다.
//		MockMvc mockMvc = mockMvcBuilder.build() ;
		
		//Step.3 RequestBuilder 객체를 생성하기 
		// (1) 전송 파라미터가 없는 경우에는 아래와 같이 하고 
//		RequestBuilder reqBuilder = MockMvcRequestBuilders.get("/board/list");
		
		//(2) 전송 파라미터까지 보내야 하는 경우에는,  아래와 같이 한다. 
//		MockHttpServletRequestBuilder reqbuilder =  MockMvcRequestBuilders.get("/board/list");
//		reqbuilder.param(전송파라미터 이름, 전송값(가변인자));
//		reqbuilder.param( "name", "koo");
//		reqbuilder.param( "age", "10");
//		reqbuilder.param( "hobby", "Book","Movie");
		
		
		//Step4. 실제 Controller로 요청(Request) 보내기 
		//	     요청을 보낸 결과로, Model & View 이름을 받을 수 있음
		//mockMvc 의 핵심 메소드는 perform 
//		ResultActions resultActions = mockMvc.perform(reqBuilder); 
		
		
		//Step.5 step4에서 발생한 Model & View 이름을 얻어낸다.
//		MvcResult mvcResult = resultActions.andReturn();
		
		// Step.6 step5에서 얻어낸 MvcResult 의 getter 메소드를 이용해서 
		//  	  Test Target 인  BoardController's handler 메소드의 수행결과인 모델과 뷰를 얻어냄
		
//		ModelAndView modelAndView = mvcResult.getModelAndView();
		
		// Step.7 ModelAndView 객체로부터 아래의 2가지 정보를 획득하여 출력
		//		(1) 반환된 뷰의 이름 (2) 반환된 비지니스 데이터 (즉, Model 객체)
//		String viewName = modelAndView.getViewName();
//		ModelMap model = modelAndView.getModelMap();
		
//		log.info("\t + viewName : {} " , viewName);
//		log.info("\t + model : {} " , model);
		
		
		//--------------- 설명없이 쭉쭊 
		
		MockMvcBuilder mockMvcBuilder = MockMvcBuilders.webAppContextSetup(ctx);
		MockMvc mockMvc = mockMvcBuilder.build() ;
		RequestBuilder reqBuilder = MockMvcRequestBuilders.get("/board/list");
		
		ModelAndView modelAndView = 
				mockMvc
				.perform(reqBuilder)
				.andReturn()
				.getModelAndView();
		
		String viewName = modelAndView.getViewName();
		ModelMap model = modelAndView.getModelMap();
		
		log.info("\t + modelAndView : {} " , modelAndView);
		// view와 model의 map형식으로 되어있는 것을 확인 

		
	}// testList

	
	
}// end class

- Controller 작성

  • InitializingBean, DisposableBean 인터페이스에 대해 보았고 구현해야하는 메소드가 어떤게 있는지 봄
  • RedirectAttributes 클래스와 그 안에 있는 메서드들 addAttribute/addFlashAttribute
@Log4j2
@AllArgsConstructor

@RequestMapping("/board/")
@Controller
public class BoardController 
	/*implements InitializingBean, DisposableBean*/ {
//InitializingBean: 빈을 초기화할 기회를 주는 인터페이스 
// DisposableBean: 생성된 빈 객체를 파괴하기 직전 콜백하는 메서드를 가지고 있는 인터페이스
	
	private BoardService service;

//	@Override
//	public void destroy() throws Exception {
//
//		
//	}//destroy
//	@Override
//	public void afterPropertiesSet() throws Exception {
//		log.trace("afterPropertiesSet() invoked.");
//		
//		assert this.service != null;
//		log.info("\t + this.service :{}", this.service);
//	}//afterPropertiesSet
	

	
	@GetMapping("/list")
	public void list(Model model) throws ControllerException {
		log.trace("list() invoked");
		
		try {
			List<BoardVO> list = this.service.getList();
			model.addAttribute("list", list); // JSP로 전달할 모델 데이터를 상자에 넣는다.
			
			
		} catch (Exception e) {
			throw new ControllerException(e);
		}
		
	}//list
	
	
	@GetMapping("/register")
	public String register(BoardDTO dto , RedirectAttributes rttrs) throws ControllerException {
		log.trace("register invoked.");
		try {
			
			boolean isRegister = this.service.register(dto);
			log.info("\t + isRegister:{}",isRegister);
			
			//1. Session Scope에 아래의 이름과 값으로 바인딩해서 전달 (공유)
			rttrs.addFlashAttribute("result", (isRegister)? "SUCCESS("+dto.getBno()+")": "FAILURE");
			// 2. Get방식의 전송파라미터 (즉, Query String 형태로 전달, 예 ? result=FAILURE )
//			rttrs.addAttribute("result", (isRegister)? "SUCCESS("+dto.getBno()+")": "FAILURE");
			
			return "redirect: /board/list";
		}catch (Exception e) {
			throw new ControllerException(e);
		}//try - catch
		
		
	}// register
	
	
	@PostMapping("/modify")
	public String modify(BoardDTO dto, RedirectAttributes rttrs) throws ControllerException{
		log.trace("modify({}) invoked.",dto);
		try {	
			boolean isModify = this.service.modify(dto);
			log.info("\t + isRegister:{}",isModify);
			
			//1. Session Scope에 아래의 이름과 값으로 바인딩해서 전달 (공유)
	//		rttrs.addFlashAttribute("result", (isModify)? "SUCCESS("+dto.getBno()+")": "FAILURE");
			// 2. Get방식의 전송파라미터 (즉, Query String 형태로 전달, 예 ? result=FAILURE )
			rttrs.addAttribute("result", (isModify)? "SUCCESS("+dto.getBno()+")": "FAILURE");
			
			return "redirect: /board/list";
		}catch (Exception e) {
			throw new ControllerException(e);
		}//try - catch
		
		
	}// modify
	
	
	@PostMapping("/remove")
	public String remove(BoardDTO dto, RedirectAttributes rttrs) throws ControllerException{
		log.trace("modify({}) invoked.",dto);
		try {	
			boolean isRemove = this.service.remove(dto);
			log.info("\t + isRegister:{}",isRemove);
			
			//1. Session Scope에 아래의 이름과 값으로 바인딩해서 전달 (공유)
	//		rttrs.addFlashAttribute("result", (isRemove)? "SUCCESS("+dto.getBno()+")": "FAILURE");
			// 2. Get방식의 전송파라미터 (즉, Query String 형태로 전달, 예 ? result=FAILURE )
			rttrs.addAttribute("result", (isRemove)? "SUCCESS("+dto.getBno()+")": "FAILURE");
			
			return "redirect: /board/list";
		}catch (Exception e) {
			throw new ControllerException(e);
		}//try - catch
	
	}// remove
	
	
	@GetMapping({"/get","/modify"})
	public void get(BoardDTO dto, Model model) throws ControllerException{
		log.trace("modify({}) invoked.",dto);
		try {	
			BoardVO vo = this.service.get(dto);
			log.info("\t + isRegister:{}",vo);
			
			model.addAttribute("board",vo);

		}catch (Exception e) {
			throw new ControllerException(e);
		}//try - catch
	
	}// remove
	
	
}// end class

위에서 리다이렉트로 targetURL 재요청시, 필요하다면 데이터를 보낼 수 있는데 이 때 필요한것이 바로 RedirectAttributters (줄여서 rttrs)
타입의 임시상자를 Spring에서 제공한다. rttrs의 2가지 메소드가
addFlashAttribute/ addAttribute 이다.

- ridirect를 정리하는 예제

webapp에 리다이렉트의 폴더를 따로 만든다.
위에처럼 Spring MVC 기반의 Controller handler method 안에서
소위 return "redirect:targetURL"

A > B > C 이런식으로 예제

~A~

<body>
    <h1>/redirect/A.jsp</h1>
    <hr>

    <!-- 웹브라우저에게 302/307/308 상태코드의 리다이렉트 응답을 전송 -->
	
    <%  // scriptlet tag : 자바코드를 작성할 때 사용하는 태그
        // 빨간색 용지에 최우선순위값 1을 기재해서, B.jsp 에게 보여주자!!!
        // 방법2가지(B.jsp에게 데이터를 보내는 방법):
        //     (1) 전송파라미터로 보내자! (즉, Query String 형태로 구성해서...)
        //     (2) 공유영역인 Session Scope에 올려놔서 보내자!(공유시킴)

        // 1st. method
        response.sendRedirect("/redirect/B.jsp?priority=1&color=red");
    %>


</body>

~B~

<body>
    <h1>/redirect/B.jsp</h1>
    <hr>

    <!-- A.jsp 쪽에서 준, 쪽지의 색깔과 우선순위값을 확인하자! -->
    <%
        String color = request.getParameter("color");
        String priority = request.getParameter("priority");        
    %>

    <!-- <h2>1. color: <%= color %></h2>
    <h2>2. priority: <%= priority %></h2> -->

    <!-- --------------- -->

    <% // Scriptlet tag

        // 2nd. method (Session Scope 공유영역을 활용)
        session.setAttribute("color", "BLUE");
        session.setAttribute("priority", "1");

        response.sendRedirect("/redirect/C.jsp");   // 302응답코드를 가진 응답문서가 전송
    %>


</body>

~C~

<body>
    <h1>/redirect/C.jsp</h1>
    <hr>

    <!-- B.jsp 쪽에서 준, 쪽지의 색깔과 우선순위값을 확인하자! -->
    <%
        // Request Scope 공유영역에 올려 놓은 공유 데이터를 활용
        String color = (String) session.getAttribute("color");
        String priority = (String) session.getAttribute("priority");        
    %>

    <h2>1. color: <%= color %></h2>
    <h2>2. priority: <%= priority %></h2>



</body>

A실행 순간 C로 전송 값과 함께 넘어간다.


- 표현할 JSP작성

- get

  • css 처리해서 정리되어보인다.
  • 비즈니스 로직에 의해 처리되어 돌아온 값에 의해 표현함

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-migrate/3.4.0/jquery-migrate.min.js"></script>

    <script>
        $(function () {
            console.clear();

            $('#listBtn').on('click', function () {
                // [ GET /board/list ] Request
                location.href = "/board/list";
            }); // .onclick

            $('#modifyBtn').click( () => {
                // [ GET /board/modify ] Request
                location.href = "/board/modify?bno=${board.bno}";
            }); //click

        }); // .jq
    </script>
</head>
<body>
    <h2>/WEB-INF/views/board/get.jsp</h2>
    <hr>
    <div id="container">
        <form action="#">
            <table>
                <caption><h3>게시글 번호: ${board.bno}</h3></caption>

                <tr>
                    <td><label for="title">제목</label></td>
                    <td><input type="text" name="title" size="50" value="${board.title}" readonly></td>
                </tr>

                <tr>
                    <td><label for="content">내용</label></td>
                    <td><textarea name="content" cols="52" rows="10" readonly>${board.content}</textarea></td>
                </tr>

                <tr>
                    <td><label for="writer">작성자</label></td>
                    <td><input type="text" name="writer" size="20" value="${board.writer}" readonly></td>
                </tr>

                <tr>
                    <td colspan="2">&nbsp;</td>
                </tr>
                
                <tr>
                    <td colspan="2">
                        <button type="button" id="modifyBtn">MODIFY</button>
                        <button type="button" id="listBtn">LIST</button>
                    </td>
                </tr>
            </table>
        </form>
    </div>

</body>

- list

  • JSTL사용 부분과 HTML table태그 사용했던 것을 참고
  • JS 부분에서 위에 사진부분 클릭시 게시글생성화면으로 넘김
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-migrate/3.4.0/jquery-migrate.min.js"></script>

    <script>
        $(function () {
            console.clear();

            $('#regBtn').on('click', function () {
                // [ GET /board/register ] Request
                location.href = "/board/new";
            }); // .onclick

        }); // .jq
    </script>

    <script>
        var result = "${param.result}";
        
        if(result != null && result.length > 0) {
            alert(result);
        } // if
    </script>
</head>
<body>
    <h2>/WEB-INF/views/board/list.jsp</h2>
    <hr>

    <div id="wrapper">
        <button type="button" id="regBtn">REGISTER</button>

        <table border="1">
            <caption>tbl_board</caption>

            <thead>
                <tr>
                    <th>bno</th>
                    <th>title</th>
                    <th>writer</th>
                    <th>insertTs</th>
                    <th>updateTs</th>
                </tr>
            </thead>

            <tbody>
                <c:forEach var="board" items="${list}">
                    <tr>
                        <td>${board.bno}</td>
                        <td><a href="/board/get?bno=${board.bno}">${board.title}</a></td>
                        <td>${board.writer}</td>
                        <td><fmt:formatDate pattern="yyyy-MM-dd HH:mm:ss" value="${board.insertTs}" /></td>
                        <td><fmt:formatDate pattern="yyyy-MM-dd HH:mm:ss" value="${board.updateTs}" /></td>
                    </tr>
                </c:forEach>
            </tbody>

            <tfoot></tfoot>
        </table>

    </div>

</body>

- modify

  • 수정할 것인지 삭제할 것인지 나타내는 폼태그와 클릭이벤트
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-migrate/3.4.0/jquery-migrate.min.js"></script>

    <script>
        $(function () {
            console.clear();

            $('#listBtn').on('click', function () {
                // [ GET /board/list ] Request
                location.href = "/board/list";
            }); // .onclick

            $('#removeBtn').click( () => {
                // [ POST /board/remove + bno ] Request
                
                let formObj = $('form');
                
                formObj.attr('action', '/board/remove');
                formObj.attr('method', 'POST');

                formObj.submit();
            }); //click

        }); // .jq
    </script>
</head>
<body>
    <h2>/WEB-INF/views/board/modify.jsp</h2>
    <hr>

    <div id="container">
        <form action="/board/modify" method="POST">
            <input type="hidden" name="bno" value="${board.bno}">

            <table>
                <caption><h3>수정할 게시글 번호: ${board.bno}</h3></caption>

                <tr>
                    <td><label for="title">제목</label></td>
                    <td><input type="text" name="title" size="50" value="${board.title}"></td>
                </tr>

                <tr>
                    <td><label for="content">내용</label></td>
                    <td><textarea name="content" cols="52" rows="10">${board.content}</textarea></td>
                </tr>

                <tr>
                    <td><label for="writer">작성자</label></td>
                    <td><input type="text" name="writer" size="20" value="${board.writer}" readonly></td>
                </tr>

                <tr>
                    <td colspan="2">&nbsp;</td>
                </tr>
                
                <tr>
                    <td colspan="2">
                        <button type="submit" id="submitBtn">SUBMIT</button>
                        <button type="button" id="removeBtn">REMOVE</button>
                        <button type="button" id="listBtn">LIST</button>
                    </td>
                </tr>
            </table>
        </form>
    </div>

</body>

- new


    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-migrate/3.4.0/jquery-migrate.min.js"></script>

    <script>
        $(function () {
            console.clear();

            $('#listBtn').on('click', function () {
                // [ GET /board/list ] Request
                location.href = "/board/list";
            }); // .onclick

        }); // .jq
    </script>
</head>

<body>
    <h2>/WEB-INF/views/board/new.jsp</h2>
    <hr>

    <div id="wrapper">
        <form action="/board/register" method="POST">
            <table>
                <tr>
                    <td><label for="title">제목</label></td>
                    <td><input type="text" name="title" size="50" placeholder="제목을 입력하세요" required></td>
                </tr>

                <tr>
                    <td><label for="content">내용</label></td>
                    <td><textarea name="content" cols="52" rows="10" placeholder="내용을 입력하세요" required></textarea></td>
                </tr>

                <tr>
                    <td><label for="writer">작성자</label></td>
                    <td><input type="text" name="writer" size="20" placeholder="작성자" required></td>
                </tr>

                <tr>
                    <td colspan="2">&nbsp;</td>
                </tr>
                
                <tr>
                    <td colspan="2">
                        <button type="submit" id="submitBtn">SUBMIT</button>
                        <button type="button" id="listBtn">LIST</button>
                    </td>
                </tr>
            </table>
        </form>
    </div>


</body>
profile
일단 흐자

0개의 댓글