[Spring Boot2][2] 7. 웹 계층 개발(1)
93920 단어 TILSpringSpringbootSpring
🏷 홈 화면과 레이아웃
✔️ 홈 컨트롤러 등록
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";
}
}
✔️ 타임리프 템플릿 등록
<!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>
- 타임리프를 사용해 대체할
fragments/header
fragments/bodyHeader
fragments/footer
파일도 만들어 준다!
⬆️ 결과 화면이 뭔가 간지가 나지 않는다😅
✔️ view 리소스 등록
📌 부트스트랩 사용 (v4.3.1)
resources/static
하위에css
,js
추가resources/static/css/jumbotron-narrow.css
추가
⬆️ 부트스트랩 추가 후 실행 결과 👍🏻
🏷 회원 등록
💡 폼 객체를 사용해서 화면 계층과 서비스 계층을 명확하게 분리한다!
✔️ 회원 등록 폼 객체
package jpabook.jpashop.controller;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotEmpty;
@Getter @Setter
public class MemberForm {
@NotEmpty(message = "회원 이름은 필수 입니다")
private String name;
private String city;
private String street;
private String zipcode;
}
✔️ 회원 등록 컨트롤러
package jpabook.jpashop.controller;
import jpabook.jpashop.domain.Address;
import jpabook.jpashop.domain.Member;
import jpabook.jpashop.service.MemberService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import javax.validation.Valid;
import java.util.List;
@Controller
@RequiredArgsConstructor
public class MemberController {
private final MemberService memberService;
@GetMapping("/members/new")
public String createForm(Model model) {
model.addAttribute("memberForm", new MemberForm());
return "members/createMemberForm";
}
@PostMapping("/members/new")
public String create(@Valid MemberForm form, BindingResult result) {
if (result.hasErrors()) {
return "members/createMemberForm";
}
Address address = new Address(form.getCity(), form.getStreet(), form.getZipcode());
Member member = new Member();
member.setName(form.getName());
member.setAddress(address);
memberService.join(member);
return "redirect:/";
}
@GetMapping("/members")
public String list(Model model) {
List<Member> members = memberService.findMembers();
model.addAttribute("members", members);
return "members/memberList";
}
}
✔️ 회원 등록 폼 화면
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments/header :: header" />
<style>
.fieldError {
border-color: #bd2130;
}
</style>
<body>
<div class="container">
<div th:replace="fragments/bodyHeader :: bodyHeader"/>
<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>
<br/>
<div th:replace="fragments/footer :: footer" />
</div> <!-- /container -->
</body>
</html>
⬆️ 회원 등록이 완료되고 DB에도 잘 반영된 것을 확인 👍🏻
🏷 회원 목록 조회
✔️ 회원 목록 컨트롤러 추가
@GetMapping("/members")
public String list(Model model) {
List<Member> members = memberService.findMembers();
model.addAttribute("members", members);
return "members/memberList";
}
- 조회한 상품을 뷰에 전달하기 위해 스프링 MVC가 제공하는 모델(
Model
) 객체에 보관 - 실행할 뷰 이름을 반환
✔️ 회원 목록 뷰
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments/header :: header" />
<body>
<div class="container">
<div th:replace="fragments/bodyHeader :: bodyHeader" />
<div>
<table class="table table-striped">
<thead>
<tr>
<th>#</th>
<th>이름</th>
<th>도시</th>
<th>주소</th>
<th>우편번호</th>
</tr>
</thead>
<tbody>
<tr th:each="member : ${members}">
<td th:text="${member.id}"></td>
<td th:text="${member.name}"></td>
<td th:text="${member.address?.city}"></td>
<td th:text="${member.address?.street}"></td>
<td th:text="${member.address?.zipcode}"></td>
</tr>
</tbody>
</table>
</div>
<div th:replace="fragments/footer :: footer" />
</div> <!-- /container -->
</body>
</html>
📌 참고 : 폼 객체 vs 엔티티 직접 사용
- 요구사항이 정말 단순할 때는 폼 객체(
MemberForm
) 없이 엔티티(Member
를 직접 등록과 수정 화면
에서 사용해도 된다!- 하지만 화면 요구사항이 복잡해지기 시작하면, 엔티티에 화면을 처리하기 위한 기능이 점점 증가한다.
- 결과적으로 엔티티는 점점 화면에 종속적으로 변하고, 이렇게 화면 기능 때문에 지저분해진 엔티티는 결국 유지보수하기 어려워진다😰
➡️ 실무에서 엔티티는 핵심 비즈니스 로직만 가지고 있고, 화면을 위한 로직은 없어야 한다!!!
🏷 상품 등록
✔️ 상품 등록 폼
package jpabook.jpashop.controller;
import lombok.Getter;
import lombok.Setter;
@Getter @Setter
public class BookForm {
private Long id;
private String name;
private int price;
private int stockQuantity;
private String author;
private String isbn;
}
✔️ 상품 등록 컨트롤러
package jpabook.jpashop.controller;
import jpabook.jpashop.domain.item.Book;
import jpabook.jpashop.domain.item.Item;
import jpabook.jpashop.service.ItemService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import java.util.List;
@Controller
@RequiredArgsConstructor
public class ItemController {
private final ItemService itemService;
@GetMapping("/items/new")
public String createForm(Model model) {
model.addAttribute("form", new BookForm());
return "items/createItemForm";
}
@PostMapping("/items/new")
public String create(BookForm form) {
Book book = new Book();
book.setName(form.getName());
book.setPrice(form.getPrice());
book.setStockQuantity(form.getStockQuantity());
book.setAuthor(form.getAuthor());
book.setIsbn(form.getIsbn());
itemService.saveItem(book);
return "redirect:/";
}
}
- 상품 등록 폼에서 데이터를 입력하고 Submit 버튼을 클릭하면
/items/new
를POST
방식으로 요청 - 상품 저장이 끝나면 상품 목록 화면(
redirect:/items
)으로 리다이렉트!
✔️ 상품 등록 뷰
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments/header :: header" />
<body>
<div class="container">
<div th:replace="fragments/bodyHeader :: bodyHeader"/>
<form th:action="@{/items/new}" th:object="${form}" method="post">
<div class="form-group">
<label th:for="name">상품명</label>
<input type="text" th:field="*{name}" class="form-control" placeholder="이름을 입력하세요">
</div>
<div class="form-group">
<label th:for="price">가격</label>
<input type="number" th:field="*{price}" class="form-control" placeholder="가격을 입력하세요">
</div>
<div class="form-group">
<label th:for="stockQuantity">수량</label>
<input type="number" th:field="*{stockQuantity}" class="form-control" placeholder="수량을 입력하세요">
</div>
<div class="form-group">
<label th:for="author">저자</label>
<input type="text" th:field="*{author}" class="form-control" placeholder="저자를 입력하세요">
</div>
<div class="form-group">
<label th:for="isbn">ISBN</label>
<input type="text" th:field="*{isbn}" class="form-control" placeholder="ISBN을 입력하세요">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
<br/>
<div th:replace="fragments/footer :: footer" />
</div> <!-- /container -->
</body>
</html>
🏷 상품 목록
✔️ 상품 목록 컨트롤러
@GetMapping("/items")
public String list(Model model) {
List<Item> items = itemService.findItems();
model.addAttribute("items", items);
return "items/itemList";
}
✔️ 상품 목록 뷰
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments/header :: header" />
<body>
<div class="container">
<div th:replace="fragments/bodyHeader :: bodyHeader"/>
<div>
<table class="table table-striped">
<thead>
<tr>
<th>#</th>
<th>상품명</th>
<th>가격</th>
<th>재고수량</th>
<th></th>
</tr>
</thead>
<tbody>
<tr th:each="item : ${items}">
<td th:text="${item.id}"></td>
<td th:text="${item.name}"></td>
<td th:text="${item.price}"></td>
<td th:text="${item.stockQuantity}"></td>
<td>
<a href="#" th:href="@{/items/{id}/edit (id=${item.id})}" class="btn btn-primary" role="button">수정</a>
</td>
</tr>
</tbody>
</table>
</div>
<div th:replace="fragments/footer :: footer"/>
</div> <!-- /container -->
</body>
</html>
회원 목록과 로직이 매우 비슷하니 자세한 로직 설명은 패쓰하겠다😉
길어지니 2탄으로 나눠쓸거임^-^*
Author And Source
이 문제에 관하여([Spring Boot2][2] 7. 웹 계층 개발(1)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@sorzzzzy/Spring-Boot22-7.-웹-계층-개발1저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)