본문 바로가기

스프링 (인프런)/스프링부트

상품 도메인 개발

1. 상품 엔티티 개발

@Entity
@Inheritance( strategy = InheritanceType.SINGLE_TABLE )
@DiscriminatorColumn( name = "dtype" )
@Getter @Setter
public abstract class Item {
    @Id @GeneratedValue
    @Column( name = "item_id" )
    private Long id;

    private String name;

    private int price;

    private int stockQuantity;

    @ManyToMany( mappedBy = "items" )
    private List<Category> categories = new ArrayList<Category>();


    // 비즈니스 로직
    public void addStockQuantity(int quantity) {        // 재고 수량 증가
        this.stockQuantity += quantity;
    }

    public void removeStockQuantity(int quantity) {     // 재고 수량 감소
        int left = this.stockQuantity - quantity;
        if (left < 0)
            throw new NotEnoughStockException("need more stock");

        this.stockQuantity = left;
    }
}

- 이전에 이미 개발한 Item에 비즈니스 로직을 추가해줬다.

- 생각해보면 Member를 다룰 때에는 필드만 추가했었기 때문에 Item 클래스에 직접 비즈니스 로직을 추가하는 것이 어색했다.

- 그런데 Member는 필드 값을 직접 바꾸는 일이 없었고, Item은 quantity를 매번 바꿔야 한다는 차이가 있었다.

- 강의에서도 객체지향을 유지하기 위해서는 Item에서 메서드를 지니는 게 응집력을 높인다고 한다.

 

- 그래서 quantity를 늘리고 줄이는 메서드를 각각 넣었다.

- 그런데 줄이는 경우에는 quantity가 0보다 작아지는 경우를 따져서 예외를 일으켜야 한다.

    - 그래서 NotEnoughStockException을 새롭게 정의하여 사용했다.

 

public class NotEnoughStockException extends RuntimeException {
    public NotEnoughStockException() {
        super();
    }

    public NotEnoughStockException(String message) {
        super(message);
    }

    public NotEnoughStockException(String message, Throwable cause) {
        super(message, cause);
    }

    public NotEnoughStockException(Throwable cause) {
        super(cause);
    }
}

 

 

 

2. 상품 리포지토리 개발

@Repository
@RequiredArgsConstructor
public class ItemRepository {
    private final EntityManager em;

    public void save(Item item) {
        if (item.getId() == null)       // persist() 이전에 item 인스턴스에는 id가 없기 때문에 if문으로 새로운 item이라는 보장을 받을 수 있다.
            em.persist(item);           // 새로운 객체로 등록한다.
        else
            em.merge(item);             // 이미 DB에 있고, update하는 것
    }

    public Item findOne(Long id) {
        return em.find(Item.class, id);
    }

    public List<Item> findAll() {
        return em.createQuery("select i from Item i", Item.class)
                .getResultList();
    }
}

- 역시나 @Repository로 스프링한테 리포지토리 역할을 하는 빈으로 등록해달라고 요청한다.

- 역시나 @RequiredArgsConstructor로 final이 붙은 EntityManager를 주입 받아 사용한다.

 

- save()를 살펴 보면 if (item.getId() == null)을 사용하고 있다.

    - 이는 이 아이템이 DB에 이미 등록되어 있는 객체인지 확인하는 것이다.

    - 되어 있지 않으면 persist()를 쓰지만, 이미 있는 Item은 업데이트의 개념을 가진 merge()를 호출한다.

 

 

 

3. 상품 서비스 개발

@Service
@Transactional( readOnly = true )
@RequiredArgsConstructor
public class ItemService {
    private final ItemRepository itemRepository;
    
    @Transactional
    public void saveItem(Item item) {
        itemRepository.save(item);
    }
    
    public List<Item> findItems() {
        return itemRepository.findAll();
    }
    
    public Item findOne(Long itemId) {
        return itemRepository.findOne(itemId);
    }
}

- 역시나 @Service로 스프링한테 서비스 역할을 하는 빈으로 등록해달라고 요청한다. 

- 역시나 @Transactional로 DB 접근 메서드들을 트랜잭션 안에 두되, 읽기 전용 메서드가 더 많아서 readOnly = false로 둔다.

- 역시나 @RequiredArgsConstructor로 ItemRepository 객체를 가져와서 쓴다.

 

- 전체적으로 ItemRepository의 기능을 그대로 가져와서 쓴다. ( 다만, ItemService가 서비스 계층이므로 더 바깥에 있다. )

 

- saveItem()은 아이템을 DB에 저장해서 DB 내용을 바꾸기 때문에 @Transactional을 따로 둔다.

    - 참고로 @Transactional의 readOnly 디폴트 값은 false다.

'스프링 (인프런) > 스프링부트' 카테고리의 다른 글

웹 계층 개발  (0) 2023.07.08
주문 도메인 개발  (0) 2023.07.07
애플리케이션 구현 준비  (0) 2023.07.05
엔티티 클래스 개발  (0) 2023.07.05
도메인 모델과 테이블 설계  (0) 2023.07.03