본문 바로가기
Spring/스프링 MVC

[스프링 MVC 1편] 7 - (2) 상품 도메인 개발

by Poorm 푸름 2023. 12. 2.

 *  스프링 입문 = window, 스프링 MVC 1편 = Mac 으로 진행합니다

 *  섹션7 부터는 자바 17 버전으로 진행합니다

 *  진도 : 섹션7 - (2) ~ (3)

 *          : 자바 클래스명,         : 코드,         : 단축키

 

 

1. 요구 사항 분석 (섹션 3 - 1 참고)

 

상품 도메인 모델

  • 상품 ID
  • 상품명
  • 가격
  • 수량

상품 관리 기능

  • 상품 목록
  • 상품 상세
  • 상품 등록
  • 상품 수정

 

< 동작 흐름 >

 

클라이언트 → 상품 목록 - 상품 상세                   

클라이언트 → 상품 등록 - 상품 저장 - 상품 상세 (내부 호출)                 

클라이언트 → 상품 수정 - 상품 상세 (redirect)

 

 

2. 도메인 개발 

 [ src - java - hello .iemservice - domain - item - Item ]

@Getter @Setter
public class item {
    private Long id;
    private String itemName;
    private Integer price;
    private Integer quantity;

    public item() {
    }

    public item(String itemName, Integer price, Integer quantity) {
        this.itemName = itemName;
        this.price = price;
        this.quantity = quantity;
    }
}

 

- 롬복 생성 @Getter @Setter

 

  • @Data 사용하면 위험 (단순한 데이터를 왔다 갔다하며 쓰는 DTO 사용할 경우에는 써도 괜찮다)

 

- 필드 생성 private ~

  • price, quantity은 null이 들어올 수 있으므로 Integer를 사용했다


- 기본 생성자 만들기 public Item() (command+N - Constructor)

 


- id 제외한 생성자 만들기 public Item()~ (command+N - Constructor)

 

 

< 그렇다면 DTO란 >

 

데이터 전송 객체(DTO)란 프로세스 사이에서 데이터를 전송하는 객체를 말한다

DTO 기법을 사용하면 중요한 정보를 노출시키지 않고 두 시스템(API와 서버 등) 간 통신을 원활하게 촉진할 수 있다

 

도메인 대신 DTO를 사용하면 좋은 이유

 

도메인 모델을 계층간 전달에 사용하면 UI 계층에서 도메인 모델의 메소드 호출 or 상태 변경 가능

  • 도메인 모델 단점
    • UI에 필요하지 않은 정보까지 가지고 있고 모든 도메인 모델 속성이 외부에 노출되면 보안 문제 발생
    • 계층간 전송에 사용하면 모델과 뷰가 강하게 결합
      (뷰의 요구사항 변화로 도메인의 코드를 변경해야할 일이 생기는 것은 좋지 않다)

도메인 모델을 캡슐화하여 보호 가능, DTO를 사용하면 이 결합을 느슨하게 만들 수 있다

 

참고사이트 : https://tecoble.techcourse.co.kr/post/2021-04-25-dto-layer-scope/

 

 

3. 리포지토리 저장소 만들기

[ src - java - hello .iemservice - domain - item - ItemRepository ]

@Repository
public class ItemRepository {
    private static final Map<Long,Item> store =new HashMap<>(); //static
    private static long sequence = 0L;

    public Item save(Item item) {
        item.setId(++sequence);
        store.put(item.getId(), item);
        return item;
    }

    public Item findById(Long id){
        return store.get(id);
    }

    public List<Item> findAll() {
        return  new ArrayList<>(store.values());
    }

    public void update(Long itemId, Item updateParam) {
        Item findItem = findById(itemId);
        findItem.setItemName(updateParam.getItemName());
        findItem.setPrice(updateParam.getPrice());
        findItem.setQuantity(updateParam.getQuantity());
    }

    public void clearStore(){
        store.clear();
    }

}

 

- 맵 생성 private static Map<Long, Item> ~

 

  • id때문에 Long 타입 설정

  • 실무에서는 HashMap는 동시에 여러 스레드가 접근하기 때문에 안쓴다
    동시성 문제 (ConcurrentHashMap, AtomicLong 사용 고려)

- id 증가를 의미하는 private static ~ sequence 생성

  • 싱글톤 패턴은 객체를 단 하나만 생생해서 공유해야 하므로 생성자를 private 접근자로 막아두고 static 사용

- 저장 Item save ~

  • item.setId (++sequence)
    set 할 때 마다 sequence 값 하나씩 올려주기
    (name은 모두 넘어온 상태 name은 내가 적는 값, id는 시스템이 정해주는 값으로서 id는 이 부분에서 설정)

  • store.put ~
    item 객체(Map)에 id 넣어주고 member 다시 store에 저장

- findById 만들기

  • id로 회원 찾기

- List<item> findAll (option + enter_java.util)

  • return new ArrayList<>(store.values())
    • 스토어에 있는 모든 값 ( = Item ) List에 넣기
    • 이렇게 하면 arraylist 조작해도 store는 영향 받지 않는다

 

- 업데이트 설정 upadate ~

  • id 로 item 찾기

  • 찾은 곳에 update 파라미터를 넣으면 업데이트 되도록 설정

- store 날리기 store.clear

  • test에 쓸 메서드

 

 

4. 테스트 코드 작성

[ src - test - java - hello - itemservice - domain - itemRepositoryTest ]

public class ItemRepositoryTest {

    ItemRepository itemRepository = new ItemRepository();

    @AfterEach
    void afterEach(){
        itemRepository.clearStore();
    }

 

  • 테스트할 대상 불러오기
  • @AfterEach : 테스트 메서드 실행 이후에 수행
  • 테스트 순서를 올바르게 하기 위해서 test가 끝날 때마다 초기화 하는 메서드 사용

< 저장 Test >

    @Test
    void save(){
        Item item = new Item("itemA", 10000,10);
        Item saveItem = itemRepository.save(item);
        Item findItem = itemRepository.findById(item.getId());
        assertThat(findItem).isEqualTo(saveItem);
    }

 

  • 테스트할 데이터 item 객체에 넣기
  • 테스트 데이터 리포지토리에  저장 (saveItem)
  • 원래 저장했던 객체에 id 꺼내서 찾기 (findItem)
  • Assertions
    • option + enter 로 assertj 설정 → option + enter Add ~ static import 설정
  • 저장된 값과 찾아온 값이 같은지 확인

< 조회 Test >

    @Test
    void findAll(){
        Item item1 = new Item("item1", 100,10);
        Item item2 = new Item("item2", 200,20);

        itemRepository.save(item1);
        itemRepository.save(item2);

        List<Item> result = itemRepository.findAll();

        assertThat(result.size()).isEqualTo(2);
        assertThat(result).contains(item1, item2);

    }

 

  • 테스트할 데이터 2개 각각 item1, item2 객체에 넣기
  • 리포지토리에 테스트할 데이터 저장
  • 조회한거 result 객체에 넣기 
    • itemRepository.findAll() option + command + v
  • 검증 단계 asserThat
    • 사이즈 2개인지 확인 / result 안에 멤버 데이터 있는지 확인

< 업데이트 Test>

    @Test
    void updateItem(){
        Item item = new Item("item1", 10000, 10);

        Item savedItem = itemRepository.save(item);
        Long itemId = savedItem.getId();
        Item updateParam = new Item("item2", 20000, 30);
        itemRepository.update(itemId,updateParam);

        Item findItem = itemRepository.findById(itemId);

        assertThat(findItem.getItemName()).isEqualTo(updateParam.getItemName());
        assertThat(findItem.getPrice()).isEqualTo(updateParam.getPrice());
        assertThat(findItem.getQuantity()).isEqualTo(updateParam.getQuantity());
    }
}

 

  • 테스트할 데이터 item 객체에 넣기
  • 리포지토리에 테스트할 데이터 저장 (savedItem)
  • id 값 가져오기
  • 업데이트 할 데이터 updateParam 객체에 저장
  • 원래 리포지토리 업데이트에 id랑 updateParam 넘기기
  • 원래 저장했던 객체에 id 꺼내서 조회하기 (findItem) option + command + v
  • 검증 단계 asserThat
    • 원래 리포지토리 업데이트에서의 값과 테스트 값 넣은 updateParam 값이 같은지 확인

 

 

 

 

 

[출처] 김영한 강사님 인프런 스프링 mvc1

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/dashboard 

 

스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 - 인프런 | 강의

웹 애플리케이션을 개발할 때 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. 스프링 MVC의 핵심 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., 원

www.inflearn.com