본문 바로가기
Spring/JPA 1

7. 웹 계층 개발

by wch_t 2023. 11. 13.

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