솜이의 데브로그

[SpringBoot] 웹 계층 개발(1) 본문

dev/Spring Boot

[SpringBoot] 웹 계층 개발(1)

somsoming 2022. 3. 25. 01:15

Reference : Inflearn 실전 스프링부트와 JPA 활용1 (김영한님 강의)

 

홈 컨트롤러 등록

package jpabook.jpashop.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@Slf4j
public class HomeController {

    @RequestMapping("/")
    public String home(){
        log.info("home controller");
        return "home";
    }
}

로그를 찍을 때

@Slf4j 어노테이션을 사용하면

Logger log = LoggerFactory.getLogger(getClass());

다음과 같이 작성하는 것과 동일하다.

(Log4j 와 비슷한 기능을 제공하는 듯 하다)

 

위와 같은 코드를 짜면, home.html 을 찾아서 그 화면이 / 을 받았을 때 뿌려지게 된다.

 

 

resources/templates/home.html

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments/header :: header">
    <title>Hello</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>

<body>

<div class="container">

    <div th:replace="fragments/bodyHeader :: bodyHeader" />

    <div class="jumbotron">
        <h1>HELLO SHOP</h1>
        <p class="lead">회원 기능</p> <p>
            <a class="btn btn-lg btn-secondary" href="/members/new">회원 가입</a>
            <a class="btn btn-lg btn-secondary" href="/members">회원 목록</a> </p>
        <p class="lead">상품 기능</p> <p>
            <a class="btn btn-lg btn-dark" href="/items/new">상품 등록</a>
            <a class="btn btn-lg btn-dark" href="/items">상품 목록</a> </p>
        <p class="lead">주문 기능</p> <p>
            <a class="btn btn-lg btn-info" href="/order">상품 주문</a>
            <a class="btn btn-lg btn-info" href="/orders">주문 내역</a> </p>
    </div>
    <div th:replace="fragments/footer :: footer" />
</div> <!-- /container -->
</body>
</html>

위와 같은 화면이 / 에서 뿌려지게 되고, 이 코드에서는 th:replace 를 통해 fragments 폴더 아래 header.html 이라는 파일, bodyHeader.html, footer.html 이라는 파일이 각각 대체하여 화면에 보여진다.

따라서 각각의 파일들을 작성해준다.

 

 

getbootstrap.com 을 통해 view 리소스를 등록할 수 있다. 예쁜 화면 가능!

resources/static 아래에 css, js 추가 가능.

 

 

회원 등록 (회원 가입)

폼 객체를 사용해 화면 계층과 서비스 계층을 분리해보자.

<a> 태그는 Link Page, 즉 일반적으로 페이지 연결을 위해 사용되며 연결 대상이 되는 페이지는 <a> 태그의 href 속성을 통해 정의한다.

 

회원 등록 컨트롤러 (MemberController)

  • @NotEmpty : java 에서 validation을 통해 spring이 validation을 해준다. (값이 비어있는지 확인)
  • Model 이란 : model.addAttribute("memberForm", new MemberForm); 으로 작성하면 컨트롤러에서 뷰로 넘어갈 때 데이터를 실어서 넘기게 된다.
  • /members/new 를 post로 받은 경우, 멤버를 새로 join 하면서 저장한 후, 재로딩되면 좋지 않기 때문에 redirect해서 home으로 보낸다. (return "redirect:/")

    • 오류가 난 경우는 BindingResult 를 이용한다. validate 한 뒤 BindingResult가 있는 경우, 오류가 result에 담겨서 코드가 실행된다.
    • 따라서 result에 에러가 있다면 return 하도록 작성.
    • 위와 같이 작성하면 이런 화면이 나타난다.

  • Spring이 에러에 대한 데이터를 찾는데, 에러가 있을 경우 다시 createMemberForm으로 보내고, 그렇게 되면 폼에서 에러가 있는지 확인한다. (BindingResult를 통해 에러를 같이 보내게 됨)

 

회원 등록 폼 화면

<form role="form" action="/members/new" th:object="${memberForm}"
          method="post">
        <div class="form-group">
            <label th:for="name">이름</label>
            <input type="text" th:field="*{name}" class="form-control" placeholder="이름을 입력하세요"
                   th:class="${#fields.hasErrors('name')}? 'form-control
  fieldError' : 'form-control'">
            <p th:if="${#fields.hasErrors('name')}"
               th:errors="*{name}">Incorrect date</p>
        </div>
        <div class="form-group">
            <label th:for="city">도시</label>
            <input type="text" th:field="*{city}" class="form-control"  placeholder="도시를 입력하세요"> </div>
        <div class="form-group">
            <label th:for="street">거리</label>
            <input type="text" th:field="*{street}" class="form-control" placeholder="거리를 입력하세요">
        </div>
        <div class="form-group">
            <label th:for="zipcode">우편번호</label>
            <input type="text" th:field="*{zipcode}" class="form-control"
                   placeholder="우편번호를 입력하세요"> </div>
        <button type="submit" class="btn btn-primary">Submit</button>
    </form>
  • 컨트롤러에서 memberForm 을 넘겨주었으므로, 화면에서는 해당 객체에 접근할 수 있게 된다.
  • th:object  -> 해당 객체를 게속 사용하겠다는 뜻.
  • th:field="*{name}"  이면 위의 object를 참고하겠다는 뜻.
  • 위의 폼 데이터가, <button> submit 을 누르면 /members/new 에 method가 post로 넘어가겠다는 뜻.
  • 그럼 이 post로 넘어온 데이터를 받는 내용을 컨트롤러에 작성.

 

회원 목록 조회

회원 목록 컨트롤러 추가 (MemberController에 추가)

  • memberService.findMembers() 하면 JPA에서 JPQL로 짜서 모든 멤버를 조회해줌
    • 가져와서 모델에 담아서, 화면에 넘겨줌.
  • 즉, 조회한 상품을 뷰에 전달하기 위해 스프링 MVC가 제공하는 모델 (Model) 객체에 보관.

 

memberList.html

  • table로 쭉 돌리면서 뿌림
  • tr th : html 태그를 그대로 가져다 쓴다. 즉, 모델에서 넘긴 members list를 그대로 가져와서 바인딩이 된다.
  • 루프로 돌리면서 그대로 찍기만 하면 나옴
  • 타임리프에서 ? 를 사용하면 null. (null 이 있으면 더이상 진행하지 않음)

 

폼 객체 사용 vs 엔티티 직접 사용

  • 요구사항이 단순한 경우에는 폼 없이 엔티티를 그대로 사용해도 된다.
  • 그러나 실무에서는 1:1로 매칭되는 경우가 거의 없기 때문에, 엔티티가 화면을 처리하기 위한 기능이 너무 많아져서 유지보수하기 어려워진다. (엔티티가 화면에 종속적으로 변하게 됨)
  • 엔티티는 최대한 순수하게 설계해야함! 즉, 엔티티는 핵심 비즈니스 로직만 가지고 있고, 화면을 위한 로직은 없어야 한다.
  • 화면이나 API에 맞는 폼 객체나 DTO를 사용하자.
  • API를 만들때는, 이유불문하고 절대 엔티티를 넘기면 안된다!!
    • 그렇게 하면 엔티티에 로직을 추가하면 api 스펙이 변화해버림.
    • 엔티티는 절대 외부로 노출해서는 안된다.
    • 템플릿 엔진에서는 서버사이드에서 돌기 때문에 선택적으로 사용은 가능함.

 

 

 

더 공부해보고 싶은 것