goblin
리니팅
goblin

공지사항

전체 방문자
오늘
어제
  • 분류 전체보기 (75)
    • 개발 (31)
      • Spring (12)
      • JPA (4)
      • JAVA (4)
      • Python (6)
      • Docker (1)
      • Error (3)
      • Spring Cloud로 개발하는 MSA (1)
    • 알고리즘 (32)
    • 자료구조 (3)
    • 컴퓨터 개론 (3)
    • 개인 프로젝트 (4)
      • 쇼핑몰 만들기 (4)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

인기 글

태그

  • JPA
  • inflearn
  • dp
  • 백준
  • 정렬
  • 문자열
  • 코딩테스트
  • 자료구조
  • 알고리즘
  • Spring
  • gradle
  • 스프링
  • 객체
  • sorting
  • 다이나믹 프로그래밍
  • 동적계획법
  • 조합
  • 클래스
  • 코딩테스트연습
  • 스프링부트
  • 파이썬
  • tdd
  • 파워자바
  • 프로그래머스
  • 구현
  • Intellij
  • python
  • springboot
  • 다이나믹프로그래밍
  • BOJ

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
goblin

리니팅

쇼핑몰 만들기 3. 상품 등록 기능 구현 - 1 (SpringBoot)
개인 프로젝트/쇼핑몰 만들기

쇼핑몰 만들기 3. 상품 등록 기능 구현 - 1 (SpringBoot)

2022. 8. 8. 20:54
728x90

❗ TDD를 적용해 개발했습니다.

 

[ 요구사항 ]

  • 상품 등록 시 상품 정보, 상품 이미지 정보 등록
  • 상품 이미지 파일의 경우 로컬 환경에 저장
  • 상품의 이미지는 최대 5개 저장. 선택한 파일이 없더라도 상품 이미지 row 자체는 저장하고, 이미지 파일명, 원본 이미지 파일명, 이미지 조회 경로는 null로 저장
  • 상품 등록 후 상품 수정 페이지로 이동

[ Repository 계층 개발 ]

먼저 ItemRepository라는 빈이 잘 띄워지는지부터 테스트를 합니다.

@DataJpaTest
public class ItemRepositoryTest {

    @Autowired
    private ItemRepository itemRepository;

    @Test
    public void ItemRepository가Null이아님() {
        assertThat(itemRepository).isNotNull();
    }

}

 

컴파일 에러를 해결하기 위해 ItemRepository를 추가합니다.

public interface ItemRepository extends JpaRepository<Item, Long> {
}

 

또 생긴 컴파일 에러를 해결하기 위해 Item이라는 entity가 필요합니다. Item entity를 생성해 주고 테스트를 실행합니다.

다음과 같은 에러가 발생했습니다.

 

org.springframework.beans.factory.BeanCreationException: 
Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]:
Invocation of init method failed; nested exception is org.hibernate.AnnotationException:
No identifier specified for entity: com.shop.arinlee.domain.item.entity.Item

내용을 보면 Item이 JPA에 의해 관리되는 클래스가 아니라고 합니다. JPA에서 관리되는 엔티티를 위해서는 기본 생성자가 필요합니다. 그러므로 @NoArgsConstructor 어노테이션을 추가해 줍니다. 

그 후 테스트를 실행하면 Item에 식별자가 없다는 에러가 나옵니다. Long 타입의 id가 Auto Increment되도록 다음 코드를 추가합니다.

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

 

그리고 테스트를 실행하면 성공입니다!

 

이제 본격적으로 Item 등록 테스트를 작성하겠습니다.

    @Test
    public void 아이템등록() {
        //given
        final Item item = Item.builder()
                .itemName("상품명")
                .itemDetail("상품설명")
                .itemSellStatus(ItemSellStatus.SELL)
                .price(30000)
                .stockNumber(2)
                .member(member)
                .build();

        //when
        final Item result = itemRepository.save(item);

        //then
        assertThat(result).isEqualTo(item);

    }

 

테스트 결과 에러가 발생하고, 해결하기 위해 다음과 같은 코드를 추가했습니다.

@Entity
@Table
@Getter
@NoArgsConstructor
public class Item {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, length = 100)
    private String itemName;

    @Column(nullable = false)
    @Enumerated(EnumType.STRING)
    private ItemSellStatus itemSellStatus;

    @Column(nullable = false)
    private int price;

    @Column(nullable = false)
    private int stockNumber;

    @Lob
    @Column(nullable = false)
    private String itemDetail;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name="member_id",nullable = false)
    private Member member;

    @Builder
    public Item(String itemName, ItemSellStatus itemSellStatus, int price, int stockNumber, String itemDetail, Member member) {
        this.itemName = itemName;
        this.itemSellStatus = itemSellStatus;
        this.price = price;
        this.stockNumber = stockNumber;
        this.itemDetail = itemDetail;
        this.member = member;
    }
}
public enum ItemSellStatus {
    SELL, SOLD_OUT;
}

 

테스트를 실행하기 전에 @Lob과 fetch에 대해 간단히 설명하겠습니다.

//@Lob

@Lob은 BLOB, CLOB 타입을 매핑하는 어노테이션입니다. CLOB란 사이즈가 큰 데이터를 외부 파일로 저장하기 위한 데이터 타입이고, BLOB는 바이너리 데이터를 DB 외부에 저장하기 위한 타입입니다. 멀티미디어 데이터를 다룰 때 사용할 수 있습니다.

 

//fetch

엔티티를 조회할 때 연관된 엔티티를 함께 조회하는 즉시 로딩과 이외에도 지연 로딩이라는 Fetch 전략이 있습니다.

즉시 로딩을 사용하면 해당 엔티티와 매핑된 엔티티도 한 번에 조회를 하기 때문에 현재 로직에서 사용하지 않을 데이터도 가져오게 됩니다. 이는 성능 문제로 이어질 수 있습니다. 규모가 작다면 큰 문제가 발생하지 않겠지만 조인하는 테이블의 수가 많아질 수록 문제가 생길 확률이 높아지겠죠?

이를 방지하기 위해서는 지연 로딩을 사용해야 합니다. 지연 로딩을 설정하면 실제 엔티티 대신에 프록시 객체를 넣어줍니다! 이는 실제로 사용되기 전까지 데이터 로딩을 하지 않고, 사용 시점에 조회 쿼리가 실행되기 때문에 성능 측면에서 훨씬 이득이라고 할 수 있습니다.

 

테스트도 성공적으로 마쳤습니다!

 

[ Service 계층 개발 ] 

필수 입력 값이 입력 되지 않았을 때, 아이템 등록이 실패하는 테스트부터 먼저 작성해보겠습니다.

결과가 null인지 확인하면 될 것 같습니다.

@ExtendWith(MockitoExtension.class)
public class ItemServiceTest {

    @InjectMocks
    private ItemService target;

    @Mock
    private ItemRepository itemRepository;

    final Member member = Member.builder()
            .email("test@email.com")
            .address("서울특별시")
            .memberName("tester")
            .password("password")
            .memberType(Type.BASE)
            .role(Role.ADMIN)
            .build();

    @Test
    public void 아이템등록실패(){
        //given
        final Item item = Item.builder()
                .itemName("상품")
                .itemDetail("설명")
                .stockNumber(3)
                .itemSellStatus(ItemSellStatus.SELL)
                .build();

        //when
        Item result = target.saveItem(item);

        //then
        assertThat(result).isNull();

    }
}

역시나 에러가 발생합니다. 이제 하나씩 구현을 하면 됩니다.

 

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class ItemService {

    private final ItemRepository itemRepository;

    public Item saveItem(Item item) {
        return itemRepository.save(item);
    }
}

saveItem 메소드를 추가하니 테스트에 통과했습니다. 다음으로 아이템 등록 성공 테스트를 작성해보겠습니다.

 

@Test
public void 아이템등록_성공(){
    //given
    final Item item = Item.builder()
            .itemName("상품")
            .itemDetail("설명")
            .stockNumber(3)
            .itemSellStatus(ItemSellStatus.SELL)
            .price(30000)
            .member(member)
            .build();
    doReturn(item).when(itemRepository).save(any(Item.class));

    //when
    final Item result = target.saveItem(item);

    //then
    assertThat(result).isEqualTo(item);
}

위에서 구현을 했기 때문에 테스트는 무난히 통과할 수 있었습니다.

 

요구사항을 보면 아이템 이미지도 등록을 해야하는데, 아이템 이미지는 테이블을 따로 만들어 사용할 예정입니다!

글이 너무 길어질 것 같아 이 부분은 다음 글에서 작성하겠습니다.

 

> TDD를 적용하기 위해 참고한 글

더보기

[Spring] TDD로 멤버십 등록 API 구현 예제 - (3/5) - MangKyu's Diary (tistory.com)

728x90
반응형

'개인 프로젝트 > 쇼핑몰 만들기' 카테고리의 다른 글

쇼핑몰 만들기 2. 회원 정보 수정 기능  (0) 2022.08.08
쇼핑몰 만들기 1. 로그인/로그아웃 구현 (Spring Security)  (0) 2022.07.16
쇼핑몰 만들기 0. 프로젝트 생성 및 환경 설정  (0) 2022.07.12
    '개인 프로젝트/쇼핑몰 만들기' 카테고리의 다른 글
    • 쇼핑몰 만들기 2. 회원 정보 수정 기능
    • 쇼핑몰 만들기 1. 로그인/로그아웃 구현 (Spring Security)
    • 쇼핑몰 만들기 0. 프로젝트 생성 및 환경 설정
    goblin
    goblin

    티스토리툴바