Server

[Spring] Spring boot와 JPA 활용 - 상품 도메인 개발

nahyeon 2022. 6. 23. 17:50

상품 엔티티 개발

 

domain/ item/Item.java

package jpabook2.jpashop2.domain.item;

import jpabook2.jpashop2.domain.Category;
import jpabook2.jpashop2.exception.NotEnoughStockException;
import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
@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<>();

	// 핵심 비지니스 로직
    /**
     * stock 증가
     */
    public void addStock(int quantity) {
        this.stockQuantity += quantity;
    }

    /**
     * stock 감소
     */
    public void removeStock(int orderQuantity) {
        int restStock = this.stockQuantity - orderQuantity;
        if(restStock < 0) {
            throw new NotEnoughStockException("need more stock");
        }
        this.stockQuantity = restStock;

    }

}

Item 엔티티 파일에 핵심 비지니스 로직 addStock() 과 removeStock() 을 넣어주었다.

데이터를 가지고 있는 엔티티 파일에 핵심 비지니스 로직을 넣어주게 되면 응집력이 좋고 관리하기 좋다. 

 

removeStock() 메서드에서 계산된 남은 재고 restStock이 0보다 작을 경우 Exception이 발생되게 했다. 만들어준 NotEnoughStockException 은 exception/NotEnoughStockException.java 파일로 만들어서 관리한다. 

 

Setter의 경우에도 @Setter 애노테이션을 사용하기보단 핵심 비지니스 로직을 통해서 setter를 설정해주는 편이 좋다.

 

exception/NotEnoughStockException.java

package jpabook2.jpashop2.exception;

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);
    }
}

RuntimeException 을 상속받아서 해당하는 메서드들을 override 해주었다. 

 

상품 리포지토리 개발

repository/ItemRepository.java

@Repository
@RequiredArgsConstructor
public class ItemRepository {
	
    private final EntityManager em;
    
    public void save(Item item) {
    	if(item.getId() == null) {
        	em.persist(item);
        } else {
        	em.merge(item);
        }
    }
    
    public void findOne(Long id) {
    	em.find(Item.class, id);
    }
    
    public List<Item> findAll() {
    	em.createQuery("select i from Item i", Item.class)
        	.getResultList();
    }
}

- save()

  • id가 null일 때
    • item은 JPA에 저장되기 전까지 id 값이 없으므로, id 값이 없다는 것은 완전히 새로 생성하는 객체라는 뜻이다.
  • id가 존재할 때
    • 이미 db에 등록되어있고 어디선가 가져온다는 뜻이다.
    • 여기서 save는 update는 아니지만 이미 데이터베이스에 저장된 엔티티를 수정한다고 보고 merge()를 실행한다.

 

 

상품 서비스 개발

service/ItemService.java

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

ItemService는 ItemRepository에 위임만 하는 클래스로, 개발이 단순하게 끝나버린다.

그러나 경우에 따라서는 위임만 하는 클래스를 만들어야 할 필요가 있는지 생각해 볼 필요가 있다. 

controller에서 바로 repository에 직접 접근해서 사용할 수 있게 만들 수도 있기 때문이다. 

 

ItemService 전체에 Transactional을 readOnly true로 설정해주었고, saveItem() 메서드는 데이터 변동이 일어나기 때문에 새롭게 @Transactionl을 적용해주었다.

 

repository에서 EntityManager에 접근하는 메서드들을 만들어주었고, Service에서는 repository의 메서드를 사용해서 Entitymanager에 접근하게끔 작성하였다.