1. MemberController
@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 memberForm, BindingResult result){
//@Valid -> validation을 사용하는구나, MemberForm내부에 필수로 사용하게 적어놓은 것을 필수로 받음
//BindingResult -> 에러가 있어도 그 내용을 여기에 담아서 실행이 됨
if(result.hasErrors()){
return "members/createMemberForm";
}
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:/";
}
@GetMapping("/members")
public String list(Model model){
List<Member> members = memberService.findMembers();
model.addAttribute("members",members);
return "members/memberList";
}
}
1-1. MemberForm
@Getter @Setter
public class MemberForm {
@NotEmpty(message = "회원 이름은 필수 입니다.")
private String name; //이름은 필수적으로 입력받도록 함
private String city;
private String street;
private String zipcode;
}
@NotEmpty
- 필수적으로 받아야 하는 정보를 명시한다.
- @NotEmpty를 설정한 필드가 있을 때 @Valid를 걸어주면 입력되지 않았을 시 에러를 뿜는다.
- 그 에러 메시지를 BindingResult에 받을 수 있음
@PostMapping("/members/new")
public String create(**@Valid MemberForm memberForm**, BindingResult result){
if(result.hasErrors()){
return "members/createMemberForm";
}
...
return "redirect:/";
}
1-2. 폼 객체 사용 이유
- Member를 사용하지 않고 MemberForm을 사용하는 이유가 무엇일까?
- 요구사항이 복잡해지게 되면 Entity에 화면을 처리하기 위한 기능이 증가하며 종속적으로 변한다.
- 유지보수가 어려워진다는 말이다.
- 실무에서 엔티티는 핵심 비즈니스 로직만 가지고 있고, 화면을 위한 로직은 없어야 한다. → 화면이나 API에 맞는 폼 객체 / DTO를 사용하자.
2. ItemController
@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 crate(BookForm bookForm){
Book book = new Book();
book.setName(bookForm.getName()); //실제 설계를 할 때는 setter를 제거하고 create메소드를 사용하자
book.setPrice(bookForm.getPrice());
book.setStockQuantity(bookForm.getStockQuantity());
book.setAuthor(bookForm.getAuthor());
book.setIsbn(bookForm.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";
}
@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.setAuthor(item.getAuthor());
form.setStockQuantity(item.getStockQuantity());
form.setIsbn(item.getIsbn());
model.addAttribute("form",form);
return "items/updateItemForm";
}
//위의 방식보다는 아래의 방식으로해야 영속성 컨텍스트의 지원을 받을 수 있다.
@PostMapping("items/{itemId}/edit")
public String updateItem(@ModelAttribute("form") BookForm form){
**itemService.updateItem(form.getId(), form.getName(),form.getPrice(),form.getStockQuantity());**
return "redirect:/items";
}
}
- Controller에서는 가급적 엔티티의 id, 변경할 data만을 넘겨주는 역할만 하도록 하자
- 대신 service에서 일 다 해줌
- 한 트랜잭션 내에서 데이터 변경이 일어나니 원자성, 일관성, 고립성, 지속성을 보장한다.
//merge는 null도 업데이트 해주니, 아래와 같은 변경 감지를 이용하자
@Transactional
public void updateItem(Long itemId, String name, int price, int stockQuantity){
Item findItem = itemRepository.findOne(itemId);
//얘는 영속 상태니까 데이터만 바꿔주면 JPA가 알아서 flush 날려준다.(바뀐애들 업데이트쿼리 디비에 날림)
//원래는 set말고 change메소드 같은 것을 만들어서 사용해야 함(역추적 가능)
findItem.setPrice(price);
findItem.setName(name);
findItem.setStockQuantity(stockQuantity);
}
2-1. 변경 감지와 병합(merge)
- 준영속 엔티티
- 영속성 컨텍스트가 더는 관리하지 않는 엔티티
- 기존 식별자를 가지고 있는 엔티티