mojo's Blog

웹 계층 개발 - (2) 본문

Spring

웹 계층 개발 - (2)

_mojo_ 2022. 9. 8. 00:47

 

상품 목록

 

상품 목록 버튼을 누르면 등록된 상품들이 화면에 나타나도록 구현해보자.

우선, 상품 목록 버튼을 누르면 /items 으로 매핑될 수 있도록 컨트롤러에 구현해보도록 한다.

@GetMapping("/items")
public String list(Model model) {
    List<Item> items = itemService.findItems();
    model.addAttribute("items", items);
    return "items/itemList";
}

 

이제 templates 아래에서 items 패키지안에 itemList.html 을 생성하여 상품 목록을 받아와서

for - loop 형태로 각 상품들에 대한 정보들을 화면에 뿌릴 수 있게 해야 한다.

 

 

현재 item 테이블에 데이터가 아무것도 없는 경우이다.

이번엔 상품을 몇 개 추가해서 보면 다음과 같다.

 

 

정상적으로 상품이 등록되어 리스트 형태로 잘 나타나는 것을 볼 수 있다.

 

 

상품 수정

 

이번엔 상품을 수정할 수 있도록 구현해보자.

 

 

위와 같이 /items/{itemId}/edit 에서 itemId 에 대한 상품을 받아올 수 있도록 컨트롤러를 구현한다.

@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";
}

 

 

위와 같이 수정 화면으로 오게 된 경우 submit 버튼을 눌렀을 때 수정 작업이 이뤄지도록 컨트롤러에서 구현한다.

@PostMapping("items/{itemId}/edit")
public String updateItem(@PathVariable("itemId") Long itemId, @ModelAttribute("form") BookForm form) {
    Book book = new Book();

    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);
    return "redirect:/items";
}

 

 

정상적으로 변경된 것을 확인할 수 있다.

그런데 /items/{itemId}/edit 와 같이 상품 정보를 수정하게 될 경우 itemId 를 사용자가 임의로

입력하여 다른 사용자의 상품을 수정하는 경우가 발생할 수 있다.

이러한 방법은 굉장히 치명적이며, 실제 사용자의 상품임을 검증할 수 있는 단계를 거친 후 상품을

수정할 수 있는 방식으로 되어야 한다.

 

그리고 다음 코드에서 수정 작업이 일어나는데 자세히 살펴보도록 하자.

itemService.saveItem(book);
@Transactional
public void saveItem(Item item) {
    itemRepository.save(item);
}
public void save(Item item) {
    if (item.getId() == null) {
        em.persist(item);
    } else {
        em.merge(item);
    }
}

 

위 과정에서 itemService => itemRepository 로 와서 최종적으로 save 메서드를 통해 변경이 되는 모습이다.

여기서 item 의 id 가 존재하기 때문에 persist 가 아닌 merge 를 하여 변경하는 것을 볼 수 있다.

merge 에 대해서 좀 더 자세히 알아보도록 해야겠다.

 

 

변경 감지와 병합(merge)

 

JPA 를 쓰면 변경 감지와 병합은 필수로 알아야 한다고 한다.

이 부분은 꼭 알아두도록 하자.

 

※ 준영속 엔티티

 

준영속 엔티티란 영속성 컨텍스트가 더는 관리하지 않는 엔티티를 말한다.

예를 들어 다음 코드를 살펴보도록 하자.

@PostMapping("items/{itemId}/edit")
public String updateItem(@PathVariable("itemId") Long itemId, @ModelAttribute("form") BookForm form) {
    Book book = new Book();

    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);
    return "redirect:/items";
}

 

여기서 클래스 Book 의 객체 book 이 결국 준영속 엔티티이다.

book 은 이미 DB 에 한번 저장되어서 식별자가 존재하는 상태이다.

이렇게 임의로 만들어낸 엔티티도 기본 식별자를 가지고 있으면 준영속 엔티티로 볼 수 있다.

 

 

- 준영속 엔티티를 수정하는 방법

 

  • 변경 감지 기능을 사용한다.
  • 병합(merge) 사용

 

※ 변경 감지 기능

 

@Transactional
public void updateItem(Long itemId, Book param) {
    Item findItem = itemRepository.findOne(itemId);
    
    findItem.setPrice(param.getPrice());
    findItem.setName(param.getName());
    findItem.setStockQuantity(param.getStockQuantity());
}

 

위 코드는 ItemService 에서 updateItem 메서드를 추가함으로써 변경작업을 추가하였다.

itemId 에 대응하는 상품을 받아오게 되면 해당 객체는 영속성 엔티티라는 것이 확실하다.

따라서 set 작업만 해주고 메서드가 종료되면 @Transactional 에 의해서 commit 되고 업데이트 쿼리가

flush 됨으로써 변경 작업이 일어나게 되는것이다.

이 방법이 더 나은 방법이라고 한다.

 

 

※ 병합

 

병합은 준영속 상태의 엔티티를 영속 상태로 변경할 때 사용하는 기능이다.

 

 

병합시 동작 방식

 

  1. 준영속 엔티티의 식별자 값으로 영속 엔티티를 조회한다.
  2. 영속 엔티티의 값을 준영속 엔티티의 값으로 병합한다.
  3. 트랜잭션 커밋 시점에 변경 감지 기능이 동작해서 데이터베이스에 UPDATE SQL이 실행

 

- 주의

변경 감지 기능을 사용하면 원하는 속성만 선택해서 변경이 가능하다.

그러나 병합을 사용하면 모든 속성이 변경된다.

병합시 값이 없을 경우 null 로 업데이트 될 수 있다. (병합은 모든 필드를 교체함)

 

 

※ 엔티티를 변경할 때는 항상 변경 감지 사용

 

컨트롤러에서 엔티티를 생성하지 않는다. (준영속 엔티티)

트랜잭션이 있는 서비스 계층에 식별자와 변경할 데이터를 명확하게 전달한다. (파라메터 or DTO)

트랜잭션이 있는 서비스 계층에서 영속 상태의 엔티티를 조회하고, 엔티티의 데이터를 직접 변경한다.

트랜잭션 커밋 시점에 변경 감지가 실행된다.

'Spring' 카테고리의 다른 글

웹 계층 개발 - (1)  (2) 2022.09.06
주문 도메인 개발  (0) 2022.09.06
상품 도메인 개발  (0) 2022.09.02
회원 도메인 개발  (1) 2022.09.02
애플리케이션 구현 준비  (2) 2022.09.02
Comments