
김영한 강사님 [실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발] 강의 참조

- @Controller : 요청 처리 클래스를 Spring Bean으로 등록하기 위한 어노테이션
- @RequestMapping 어노테이션으로 다수의 요청 처리 메소드를 작성하여 클라이언트의 요청에 대한 처리 가능
- 로그 구현체 사용
Logger log = LoggerFactory.getLogger(getClass());(slf4j)
- 롬북을 이용해서 @Slf4j 으로도 사용 가능
- log.info("a") : a라는 메시지를 info 수준으로 로그에 기록해라
package jpabook.jpashop.controller;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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";
}
}
'템플릿 엔진'의 일종. html 태그에 속성을 추가해 페이지에 동적으로 값을 추가하거나 처리
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments/header :: header">
템플릿 엔진을 사용하여 코드를 모듈화하고, 코드의 재사용성을 높일 수 있습니다.
Thymeleaf 스타일
- 문법
th:object="${memberForm}": memberform 객체th:field="*{city}"=>id="city" name="city": 객체 안에 있는 city라는 필드
- 문법은 Thymeleaf 홈페이지 docs에 상세히 나와있으므로 참조
<!-- 특정 url로 이동 -->
<a th:href="@{https://www.a.com}"></a>
<!-- 현재 서버 내에서 이동 -->
<a th:href="@{/board/list}">
<!-- 파라미터를 넘길 시 -->
<a th:href="@{/board/view(id = ${board.id})}">
<!-- 파라미터를 여러 개 넘길 시 -->
<a th:href="@{/board/view(id = ${board.id}, writer = ${board.writer}})}">
<!-- PathVariable 사용 시 -->
<a th:href="@{/board/view/{id}(id = ${board.id})}">
<!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 style="font-weight: bold; color:navy;">🐳 Welcome to JPA SHOP 🐳</h1>
<br>
<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>


검증
return "redirect:/"; : 재로딩하지 않고 이동
th:class="${#fields.hasErrors('name')}? 'form-control fieldError' : 'form-control'"
<p th:if="${#fields.hasErrors('name')}" th:errors="*{name}">
@Controller
@RequiredArgsConstructor
public class MemberController {
private final MemberService memberService;
//회원가입 form
@GetMapping("/members/new")
public String createForm(Model model){
model.addAttribute("memberForm", new MemberForm());//validation 해줌
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:/";
}
}

${member.address?.city} : ?를 사용하면 null 을 무시한다. //회원 전체 조회
@GetMapping("/members")
public String list(Model model) {
List<Member> members = memberService.findMembers();
model.addAttribute("members", members);
return "members/memberList";
}
@NotNull : null 값만 허용하지 않음. "", " " 가 입력되었을 경우는 허용
@NotEmpty : null과 추가로 "" 입력도 허용하지 않음. 다만 " " 의 입력은 허용
@NotBlank : null, "", " " 모두 허용하지 않음

@Controller
@RequiredArgsConstructor
public class ItemController {
private final ItemService itemService;
//상품등록 form
@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:/";
}
}

//상품 전체 조회
@GetMapping("/items")
public String list(Model model){
List<Item> items = itemService.findItems();
model.addAttribute("items", items);
return "items/itemList";
}

//상품 수정 form
@GetMapping("items/{itemId}/edit")
public String updateItemForm(@PathVariable("itemId") Long itemId, Model model){
Book item = (Book) itemService.findOne(itemId);
BookForm form = new BookForm();
form.setId(item.getId());
form.setName(item.getName());
form.setPrice(item.getPrice());
form.setStockQuantity(item.getStockQuantity());
form.setAuthor(item.getAuthor());
form.setIsbn(item.getIsbn());
model.addAttribute("form", form);
return "items/updateItemForm";
}
//상품수정 처리
@PostMapping("items/{itemId}/edit")
public String updateItem(@PathVariable String itemId, @ModelAttribute("form") BookForm form){//updateform의 변경값 가져오기
Book book = new Book();
book.setId(form.getId());
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:/items";
}
준영속 엔티티 : 영속성 컨텍스트가 더는 관리하지 않는 엔티티
준영속 엔티티를 수정하는 2가지 방법
- 변경 감지(dirty checking) 기능 사용 (권장!!)
@Transactional void update(Item itemParam) { //itemParam: 파리미터로 넘어온 준영속 상태의 엔티티 Item findItem = em.find(Item.class, itemParam.getId()); //같은 엔티티 조회 findItem.setPrice(itemParam.getPrice()); //데이터를 수정한다. }
- 영속성 컨텍스트에서 엔티티를 다시 조회한 후에 데이터를 수정
- 트랜잭션 안에서 엔티티를 다시 조회, 변경할 값 선택 트랜잭션 커밋 시점에 변경 감지(Dirty Checking)이 동작해서 데이터베이스에 UPDATE SQL 실행
- 병합(merge) 사용
@Transactional void update(Item itemParam) { //itemParam: 파리미터로 넘어온 준영속 상태의 엔티티 Item mergeItem = em.merge(itemParam); }
- 준영속 상태의 엔티티를 영속 상태로 변경할 때 사용
- 병합시 동작 방식
- 준영속 엔티티의 식별자 값으로 영속 엔티티를 조회한다.
- 영속 엔티티의 값을 준영속 엔티티의 값으로 모두 교체한다.(병합한다.)
- 트랜잭션 커밋 시점에 변경 감지 기능이 동작해서 데이터베이스에 UPDATE SQL이 실행
- 주의: 변경 감지 기능을 사용하면 원하는 속성만 선택해서 변경할 수 있지만, 병합을 사용하면 모든 속성이 변경된다. 병합시 값이 없으면 null 로 업데이트 할 위험 (병합은 모든 필드를 교체한다.)
@RequestParam("가져올 데이터이름")
@ModelAttribute (param) : 매개변수에 저장된 값을 속성값으로 저장

//주문생성 form
@GetMapping("/order")
public String createForm(Model model){
List<Member> members = memberService.findMembers();
List<Item> items = itemService.findItems();
model.addAttribute("members", members);
model.addAttribute("items", items);
return "order/orderForm";
}
//주문 생성
@PostMapping("/order")
public String order(@RequestParam("memberId") Long memberId,
@RequestParam("itemId") Long itemId, @RequestParam("count") int count){
orderService.order(memberId, itemId, count);
return "redirect:/orders";
}

- Thymeleaf를 이용한 반복문 해석
<option th:each="status : ${T(jpabook.jpashop.domain.OrderStatus).values()}" th:value="${status}" th:text="${status}">option </option>
- jpabook.jpashop.domain.OrderStatus 클래스의 모든 열거형 상수들을 배열 형태로 가져옴
- th:each 속성에서 status라는 이름의 변수를 선언하고, 열거형 상수들 값의 배열 요소들을 순회하면서 <option> 태그를 생성
- 각 <option> 태그에는 th:value 속성에 status 변수의 값을, th:text 속성에는 status 변수의 문자열 표현을 넣어줌
- 이렇게 생성된 <option> 태그들은 <select> 태그 내부에서 사용되며, 해당 열거형 상수를 선택 가능
//주문 목록 조회
@GetMapping("/orders")
public String orderList(@ModelAttribute("orderSearch") OrderSearch orderSearch, Model model){
List<Order> orders = orderService.findOrders(orderSearch);
model.addAttribute("orders", orders);
return "order/orderList";
}
- javascript
- document.body.appendChild(폼 변수명); : 부모태그변수명.appendChild(자식태그변수명)으로 Body태그에 생성한 html을 붙여 submit을 할 수 있게 만들어준다.
function cancel(id) { var form = document.createElement("form"); form.setAttribute("method", "post"); form.setAttribute("action", "/orders/" + id + "/cancel"); document.body.appendChild(form); form.submit(); }

//주문 취소
@PostMapping("/orders/{orderId}/cancel")
public String cancelOrder(@PathVariable("orderId") Long orderId){
orderService.cancelOrder(orderId);
return "redirect:/orders";
}