홈 화면과 레이아웃
뷰 템플릿 변경사항을 서버 재시작 없이 즉시 반영할 때는
1. spring-boot-devtools 추가
2. html 파일을 build->recompile
디자인을 위해 bootstrap을 사용했다. css, js폴더를 resources/static 하위에 추가한다.
회원 등록
먼저 회원 가입 페이지를 만들기 위해서
폼 객체를 사용해서 화면 계층과 서비스 계층을 명확하게 분리한다.
회원 등록 폼 객체
controller/MemberForm.java
@Getter @Setter
public class MemberForm {
@NotEmpty(message = "회원 이름은 필수입니다.")
private String name;
private String city;
private String street;
private String zipcode;
}
- javax의 validation을 통해서 spring이 validation을 해준다.
회원 등록 컨트롤러
controller/MemberController.java
@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> member = memberService.findMembers();
model.addAttribute("members", members);
return "members/memberList";
}
}
- 회원 등록
- createForm()
- model.addAttribute() 에서 controller에게 이 데이터(빈 껍데기 멤버 객체)를 실어서 넘기게 된다.
- MemberForm 폼이 아닌 Member 엔티티를 넘길 수도 있지만, 엔티티와는 안 맞는 부분들이 있고 스프링에서 처리하는 유효성 검사등 다른 부분들이 존재하기 때문에 실무에선 거의 불가능하다.
- MemberForm 데이터를 넘겨받아서 controller에서 한 번 정제하는 것이 좋다.
- create()
- javax.validation이 @NotEmpty 등 다양한 기능을 사용할 수 있게 해준다.
- BindingResult 덕분에 members/createMemberForm.html 파일까지 에러가 있다면, 스프링이 에러사항을 끌고가서 뿌려줄 수 있다.
- 전달받은 MemberForm form 객체로 Member 객체를 새로 만들어서 정제해주고 memberService.join()을 사용해서 회원을 등록한다. 후에 index.html 페이지로 redirect 시킨다.
- list()
- 조회한 상품을 뷰에 전달하기 위해 스프링 MVC가 제공하는 모델(Model) 객체에 보관한다.
- 모델을 전달할 때 members 키에 찾은 회원 목록 members를 담아서 해당하는 html파일로 전달한다.
- createForm()
회원 등록 폼 화면(templates/members/createMemberForm.html)
폼 객체 vs 엔티티 직접 사용
화면 요구사항이 복잡해지면 엔티티에 화면울 처리하기 위한 기능이 점점 증가한다. 엔티티는 화면에 종속적으로 변하고, 화면 기능 때문에 복잡해지게 되면 결국 유지보수가 어려워진다.
엔티티는 핵심 비지니스 로직만 가지고 있고, 화면을 위한 로직은 화면이나 API에 맞는 폼 객체나 DTO를 사용하는 것을 권장한다.
상품 수정
상품 수정은 JPA에서 변경 감지와 병합(merge) 두 가지 방법이 있다.
둘 중에 어떤 방법을 사용하는 것이 더 정석적인 방법일까
변경 감지와 병합(merge)
JPA에서 둘의 차이는 백프로 이해해야한다.
준영속 엔티티란
영속성 컨텍스트가 더는 관리하지 않는 엔티티
itemService.saveItem(book);
ItemController.java에서 @PostMapping으로 updateItem() 에서
다음과 같은 코드가 있을 때, 수정을 시도하는 객체는 Book 객체다.
Book 객체는 이미 DB에 한 번 저장되어서 식별자가 존재한다.
임의로 만들어낸 엔티티도 기존 식별자를 가지고 있으면 준영속 엔티티로 볼 수 있다.
준영속 엔티티를 수정하는 2가지 방법
- 변경 감지 기능 사용
- 병합 (merge) 사용 - 실무에서 잘 사용하지 않는다.
변경 감지(권장)
- 직접 조회해서 update 칠 필드만 변경(e.g.setName())해서 반환해야 한다.
- Spring의 Transactional에 의해서 transaction이 commit 된다.
- 영속성 컨텍스트의 entity에서 변경된 값들을 다 찾아서 바뀐 값으로 바꿔서 DB에 update 하는 것
혹은 하나하나 setPrice(), setName(), setStockQuantity() 이런 식으로 set 하다보면 어디서 set하는건지 보기가 쉽지 않다.
그러므로 의미 있는 메서드를 만들고 set을 한 번에 할 수 있는 것도 좋은 방법이다. (코드로 역추적해서 어디서 변경하는건지 바로 알 수 있기 때문)
e.g. findItem.change(price, name, stockQuantity);
controller/ItemController.java
@PostMapping("/items/{itemId}/edit")
public String updateItem(@PathVariable Long itemId, @ModelAttribute("form") BookForm form) { // 굳이 PathVariable 쓸 필요 없음. form에서 넘어오므로
// Book book = new Book();
// book.setIsbn(form.getIsbn());
// 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);
itemService.updateItem(itemId, form.getName(), form.getPrice(), form.getStockQuantity());
return "redirect:/items";
}
만약 변경할 게 많다면, 서비스 계층에 dto를 하나 만들자. (e.g.service/UpdateItemDTO.java)
- 트랜잭션이 있는 서비스 계층에 식별자(id)와 변경할 데이터를 명확하게 전달한다. (파라미터 or dto)
- 트랜잭션이 있는 서비스 계층에서 영속 상태의 엔티티를 조회하고, 엔티티의 데이터를 직접 변경한다.
- 트랜잭션 커밋 시점에 변경 감지가 실행된다.
병합의 동작 방식
1. 준영속 엔티티의 식별자로 영속 엔티티를 조회
2. 영속 엔티티의 값을 준영속 엔티티의 값으로 모두 교체(병합한다)
3. 트랜잭션 커밋 시점에 변경 감지 기능이 동작해서 데이터베이스에 UPDATE SQL이 실행
변경 감지 기능을 사용하면 원하는 속성만 선택해서 변경할 수 있지만, 병합을 사용하면 넘어온 parameter로 모든 속성이 변경된다.
병합시; 값이 없으면 null로 업데이트 할 위험이 있다. (병합은 모든 필드를 교체)
실무는 복잡하기 때문에 모든 필드를 변경하는 것은 거의 불가능하다. 또 실무에서는 보통 변경가능한 데이터만 노출하기 때문에, 병합을 사용하는 것이 오히려 번거로우므로
엔티티를 변경할 때는 항상 변경 감지(dirty checking)을 사용하자.
[ref]
'Server' 카테고리의 다른 글
[Spring] @NotNull, @NotEmpty, @NotBlank, @Null 의 차이점 (0) | 2022.08.02 |
---|---|
[Spring] Spring boot와 JPA 활용 - 주문 도메인 개발 (0) | 2022.06.24 |
[Spring] Spring boot와 JPA 활용 - 상품 도메인 개발 (0) | 2022.06.23 |
[Spring] Spring boot와 JPA 활용 - 애플리케이션 구현 준비, 회원 도메인 개발 (0) | 2022.06.19 |
[Spring] Spring boot와 JPA 활용 - 도메인 분석 설계 (0) | 2022.06.05 |