1-7. 웹 계층 개발

지니🧸·2023년 2월 5일
0

Spring Boot & JPA

목록 보기
7/35

본 문서는 인프런의 실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발 (김영한) 강의를 공부하며 작성한 개인 노트입니다.

🦞 홈화면과 레이아웃

src/main/java/jpabook.jpashop/controller/에 Controller 클래스 모음

@Controller
public class HomeController {

    @RequestMapping("/") //첫화면
    public String home() {
        return "home"; //home.html로 thymeleaf 파일 찾아감
    }
    
}

Thymeleaf styles

Include style

  • 매번 include해야함

Hierarchical-style

  • 실무에서 유용함
  • 코드의 중복 적음

디자인 리소스 - getbootstrap.com

🐙 회원 등록

MemberForm

Member 엔티티와 MemberForm의 분리:

  • 컨트롤러에서 데이터를 받아올 때의 validation과 실제 도메인에서 원하는 validation의 차이
  • 엔티티는 핵심 비즈니스 로직만 가지고 있어야함 (화면을 위한 로직 포함 X)
    src/main/java/jpabook.jpashoop/controller/MemberForm
@Getter @Setter
public class MemberForm {

    @NotEmpty(message = "회원 이름은 필수입니다.")
    private String name;

    private String city;
    private String street;
    private String zipcode;

}
  • 회원 구성에 필요한 모든 변수 포함
  • 필수 변수는 @NotEmpty 처리

MemberController

@Controller
@RequiredArgsConstructor
public class MemberController {

    ...

    @GetMapping("/members/new")
    public String createForm(Model model) {
        model.addAttribute("memberForm", new MemberForm());
        return "members/createMemberForm";
    }
}
  • model: 컨트롤러에서 뷰로 넘어갈 때 model에 데이터를 실어서 넘김
  • 리턴되는 스트링 값: 다음 순서인 html 파일
@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:/";

}
  • @Valid: MemberForm 클래스에서 @NotEmpty 처리 등이 모두 valid하게 입력되었는지
  • return "redirect:/": 첫페이지로 이동
  • BindingResult
    • 에러가 생기면 result에 담김
    • 에러가 있으면 다시 폼으로 return
    • 해당 입력칸 아래에 에러 뜨는 것은 연관 html에서 처리
      • <p th:if="${#fields.hasErrors('name')}" th:errors="*{name}">Incorrect date</p>

createMemberForm.html

<input type="text" th:field="*{name}" class="form-control"
                   placeholder="이름을 입력하세요"
                   th:class="${#fields.hasErrors('name')}? 'form-control
fieldError' : 'form-control'">
  • *{name}: *을 앞에 붙이면 넘겨 받은 model (여기서는 memberForm)을 참조하여 필드 가져오는 것
  • th:field=: 타임리프에서 field=에 지정된 필드명을 id와 name으로 자동 지정함

🐩 회원 목록 조회

컨트롤러에서 멤버리스트

  • 멤버리스트 메서드에서 members 엔티티를 그대로 반환하지만, API에서 만들 때는 절대 엔티티를 그대로 반환하면 안된다
    • 엔티티를 반환하면 엔티티에 필드 (패스워드 등)를 추가하면:
      • 그대로 노출됨
      • API 스펙이 변함
  • DTO 변환 추천

memberList.html

<tr th:each="member : ${members}">
  <td th:text="${member.id}"></td>
  <td th:text="${member.name}"></td>
  <td th:text="${member.address?.city}"></td>
  ...
  • ?: null 무시하고 스탑

🐢 상품 등록

src/main/java/jpabook.jpashop/controller/ItemController

@PostMapping(value = "/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());

    ...
    }
  • 컨트롤러에서 setter를 호출하는 것보다 createBook과 같은 create method를 엔티티에 static하게 만드는 것이 더 좋음

🦨 상품 수정

  • merge 사용
  • updateItemForm > updateItem 구분

🪵 변경 감지와 병합 (merge)

준영속 엔티티

준영속 엔티티: 영속성 컨텍스트(entity manager)가 더는 관리하지 않는 엔티티 > JPA가 관리하지 않음
(예) update 안의 book 객체

  • book 객체는 이미 디비에 한 번 저장돼서 식별자가 존재
  • 임의로 만들어낸 엔티티도 기존 식별자를 갖고 있으면 > 준영속 엔티티

영속 엔티티

  • JPA과 관리하는 영속 엔티티는 변경 감지가 일어남
  • JPA가 변화를 모두 감지하고 있다가 transaction commit 시점에 알아서 반영함

준영속 엔티티를 수정하는 방법

  1. 변경 감지 기능(Dirty Checking) 사용
    service에 update 메서드 만들기 > save할 필요 없이 변경감지에 의해 디비에 반영됨
@Transactional
public void updateItem(Long itemId, Book param) {
    Item findItem = itemRepository.findOne(itemId);
    findItem.setPrice(param.getPrice());
    findItem.setName(param.getName());
    findItem.setStockQuantity(param.getStockQuantity());
}
  • 영속 컨텍스트에서 엔티티를 다시 조회한 후에 데이터 수정
  • 트랜잭션 안에서 엔티티를 다시 조회 & 변경할 값 선택 > 트랜잭션 커밋 시점에 변경 감지 > 디비에 UPDATE SQL 실행
  1. or 병합(merge) 사용
    준영속 상태의 엔티티를 영속 상태로 변경할 때 쓰이는 기능
@Transactional
void update(Item itemParam) { //itemParam: 파라미터로 넘어온 준영속 상태의 엔티티
	Item mergeItem = em.merge(itemParam);
}

병합 동작 방식

(1) merge() 실행
(2) 파라미터로 넘어온 준영속 엔티티의 식별자 값으로 1차 캐시에서 엔티티 조회 (만약 1차 캐시에 엔티티 존재X > 디비에서 조회 후 1차 캐시에 저장)
(3) 조회한 영속 엔티티(mergeMember)에 member 엔티티 값을 넣음
(4) 영속 상태인 mergeMember 반환

  • 기존에 파라미터로 넘어온 준영속 엔티티 값은 영속으로 변화하지 않음
  • 변경감지 기능은 원하는 속성만 선택해서 변경 가능.
  • 병합은 모든 속성이 변경됨. > 병합시 값이 없으면 null로 업데이트될 위험 존재.
  • merge보다는 변경감지를 사용하자
  • save()는 식별자(Id)가 자동생성되어야 정상 동작
  • service에 UpdateItemDto 클래스를 만들어 컨트롤러 파라미터로 전달하는 것이 더 좋음

🪴 상품 주문

@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";
}
  • @RequestParam - form에서 입력받아온 데이터
    • 괄호 안에는 form에서 label한 name이 들어감
  • orderService.order(memberId, itemId, count) - member, item 등 객체를 직접 넣는 것보다 id를 넘기는 것이 더 효율적임

🫧 주문 목록 검색, 취소

주문 목록 검색/취소 html 페이지

<div class="form-group mx-sm-1 mb-2">
	<select th:field="*{orderStatus}" class="form-control">
		<option value="">주문상태</option>
		<option th:each="status : ${T(jpabook.jpashop.domain.OrderStatus).values()}"
				th:value="${status}"
				th:text="${status}">option
		</option>
	</select>
</div>
<button type="submit" class="btn btn-primary mb-2">검색</button>
profile
우당탕탕

0개의 댓글