1. 기본 디자인
resource.static 경로에 부트스트랩(css / js) 코드를 추가해준다.
2. @NotEmpty와 @Valid 어노테이션
1) @NotEmpty
- 필드 값이 null과 "" 둘 다 허용하지 않게 한다.
→ 위 경우에 에러 발생
public class MemberForm {
@NotEmpty(message = "회원 이름은 필수입니다.")
private String name;
}
2) @Valid
- 데이터 바인딩 및 유효성 검증을 수행하는데 사용된다.
@PostMapping("members/new")
public String create(@Valid MemberForm memberForm, BindingResult result) {
if (result.hasErrors())
return "members/createMembersForm";
//Member 생성 로직..
return "redirect:/";
}
3. Form 객체 / DTO 사용 유무
1) Form 객체, DTO
- 주로 화면에서 필요한 데이터를 담고 있는 객체
- 엔티티에서 화면에 불필요한 정보를 가지지 않도록 하기 위해 사용된다.
- 컨트롤러에서 엔티티 대신에 폼 객체나 DTO를 사용하여 화면과 상호작용한다.
2) API 개발 원칙
- API에서는 절대로 엔티티를 직접 반환하지 않는 것이 좋다.
- 엔티티에 로직이 추가되거나 필드가 변경될 경우, API 스펙이 변할 수 있다.
또한, 추가된 필드가 있다면 그대로 노출된다.
- 엔티티는 화면이나 API와 같은 외부 요소와의 의존성을 최소화해야 한다.
3) DTO 사용 이유
- 엔티티의 변화에 민감하지 않도록 하기 위함이다.
- DTO를 통해 API의 스펙을 관리하고, 필요한 정보만 노출시킬 수 있다.
4. @PathVariable, @ModelAttribute, @RequestParam
1) @PathVariable
- URL 경로에 포함된 변수 값을 메소드의 파라미터로 바인딩하는데 사용된다.
ex. https://wch-0625.tistory.com/48
- 주로 RestFul 웹 서비스에서 클라이언트가 전달한 경로 변수 값을 추출할 때 활용된다.
@GetMapping("/users/{userId}")
public ResponseEntity<User> getUserById(@PathVariable Long userId) {
// userId 변수에는 경로에서 추출된 값이 자동으로 할당됨
// 비즈니스 로직 수행
// ...
return new ResponseEntity<>(user, HttpStatus.OK);
}
2) @ModelAttribute
- 클라이언트가 요청 시 전달하는 값을 오브젝트 형태로 매핑해주는 어노테이션
- form 데이터를 바인딩하고, 모델(or 폼)에 필요한 데이터를 추가하여 뷰에 전달할 수 있다.
- 주로 HTML 폼에서 전송된 데이터를 모델 객체에 바인딩할 때 사용된다.
@PostMapping("/items/{itemId}/edit")
public String updateItem(@PathVariable("itemId") Long itemId, @ModelAttribute("form") BookForm form) {
itemService.updateItem(itemId, form.getPrice(), form.getName(), form.getStockQuantity());
return "redirect:/items";
}
3) @RequestParam
- 클라이언트가 요청 시 전달하는 값을 Controller의 매개변수로 1:1 매핑해주는 어노테이션
@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";
}
@ModelAttribute, @RequestParam : https://galid1.tistory.com/769
5. 보안 취약점 (Form ID 넘어올 때)
폼에서 넘어올 때 누군가 임의적으로 id를 조작해서 넘길 수 있다.
이 때 유저가 이 item(위 예시)에 대해서 권한이 있는지 없는지 확인을 해주어야 한다.
(이런 부분을 Spring Security를 사용해서 해주는 건가?)
6. 변경 감지(dirty checking)와 병합(merge)
준영속 엔티티
- 영속성 컨텍스트가 더는 관리하지 않는 엔티티
변경 감지
- 영속성 컨텍스트에서 엔티티를 다시 조회한 후에 데이터를 수정하는 방법
- 영속성 컨텍스트에서 itemId로 Item 객체를 찾는다.
그리고 파라미터에 있는 값(form)으로 변경이 필요한 데이터만 update를 적용한다.
jpa가 이러한 변경을 감지하여 커밋될 때 반영이 된다.
// 위 코드와 같음
@PostMapping("/items/{itemId}/edit")
public String updateItem(@PathVariable("itemId") Long itemId, @ModelAttribute("form") BookForm form) {
itemService.updateItem(itemId, form.getPrice(), form.getName(), form.getStockQuantity());
return "redirect:/items";
}
변경
- merge일 경우, Item 모든 파라미터의 값이 update가 적용된다.
이 때 form 입력값이 Null일 경우, 기존에 있는 필드가 유지되는 것이 아니라 Null로 update 된다.
7. 전체적인 흐름 정리
[Member]
1) "회원 등록" Form으로 이동한다.
- 이 때, 클라이언트의 요청(입력값)을 MemberForm 객체에 저장한다.
@Controller
@RequiredArgsConstructor
public class MemberController {
private final MemberService memberService;
@GetMapping("members/new")
public String createForm(Model model) {
model.addAttribute("memberForm", new MemberForm());
return "members/createMembersForm";
}
}
2) 클라이언트 POST 요청을 받아,
- 유효성이 유효하지 않으면, 다시 "회원 등록" Form으로
- 유효한 경우,
비즈니스 로직(memberService)에서 Member를 DB에 저장하고 "초기 화면"으로 이동한다.
public class MemberController {
@PostMapping("members/new")
public String create(@Valid MemberForm memberForm, BindingResult result) {
if (result.hasErrors())
return "members/createMembersForm";
Address address = new Address(memberForm.getCity(), memberForm.getStreet(), memberForm.getZipcode());
Member member = new Member();
member.setUsername(memberForm.getName());
member.setAddress(address);
memberService.join(member);
return "redirect:/";
}
}
3) DB에 저장된 Member를 모두 불러와 List에 저장한다.
그리고 View에 전달될 모델에 addAttribute() 한다.
"회원 조회" Form으로 이동한다.
public class MemberController {
@GetMapping("/members")
public String list(Model model) {
List<Member> members = memberService.findMembers();
model.addAttribute("members", members);
return "members/memberList";
}
}
[Item 수정]
1) itemId로 수정하고자 하는 Item의 form을 만들어, 클라이언트에게 전송한다.
- 이 때, 해당 Item의 정보를 받아와 form의 데이터를 채워준다. (기존 데이터 입력값에서 수정)
- 그리고 잊지않고 model.addAttribute()로 View에서 사용하게 될 model 값을 추가해주어야 한다.
@Controller
@RequiredArgsConstructor
public class ItemController {
private final ItemService itemService;
@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";
}
}
2) updateForm에서 수정할 Item과 Form 데이터를 받아, 비즈니스 로직(itemService)에서 DB에 반영해준다.
그리고 "상품 조회" Form으로 이동한다. (잘 수정되었는지)
public class ItemController {
@PostMapping("/items/{itemId}/edit")
public String updateItem(@PathVariable("itemId") Long itemId, @ModelAttribute("form") BookForm form) {
itemService.updateItem(itemId, form.getPrice(), form.getName(), form.getStockQuantity());
return "redirect:/items";
}
}
'Spring > JPA 1' 카테고리의 다른 글
6. 주문 도메인 개발 (0) | 2023.11.07 |
---|---|
5. 상품 도메인 개발 (0) | 2023.11.06 |
4. 회원 도메인 개발 (0) | 2023.11.06 |
3. 애플리케이션 구현 준비 (0) | 2023.11.06 |
2. 도메인 분석 설계 (0) | 2023.08.16 |