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;
}
@PostMapping("/members/new")
public String create(**@Valid MemberForm memberForm**, BindingResult result){
    if(result.hasErrors()){
        return "members/createMemberForm";
    }
    ...
    return "redirect:/";
}

1-2. 폼 객체 사용 이유

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";
    }
}
//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)