@Entity
@Getter
@Setter
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn
public abstract class Item {
@Id
@GeneratedValue
@Column(name = "item_id")
private Long id;
private String name; //제품 이름
private int price; //제품 가격
private int quantity; //제품 수량
@ManyToMany(fetch = FetchType.LAZY, mappedBy = "items") //실무에서는 다대다 관계 사용 X, but, 현재는 공부하는 중이기 때문에 다대다 관계로 설정 후 학습
private List<Category> categories;
/**
* == 비즈니스 로직 ==
* 도메인 주도 설계 => 엔티티에서 구현 가능한 비즈니스 로직들은 엔티티에서 바로 구현(ex: 수량 증가/감소) => 객체 지향적
* ex: quantity는 엔티티에 있으므로, quantity의 값만 변하는 비즈니스 로직은 엔티티에서 바로 구현하는 것이 객체 지향적 & 응집력 있음
*/
public void increaseStock(int quantity) {
this.quantity += quantity;
}
public void decreaseStock(int quantity) {
int restStock = this.quantity - quantity;
if (restStock < 0) {
throw new NotEnoughStockException("Need More Stock"); //남은 갯수가 0보다 작을때, 직접 개발한 exception을 던지도록 설계
}
this.quantity = restStock;
}
}
increaseStock(), decreaseStock() 같은 비즈니스 로직들을 ItemService대신 엔티티에서 바로 구현 했습니다. 이러한 이유는, 해당 비즈니스 로직들에서는 quantity의 값만 변경하기 때문에 엔티티에서 바로 구현하는 것이 객체 지향적이며 응집력이 있기 때문입니다.
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);
}
protected NotEnoughStockException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
RunTimeException을 상속 받고 메서드들을 구현함으로써 자체적인 exception을 개발할 수 있습니다.
@Repository
@RequiredArgsConstructor
public class ItemRepository {
private final EntityManager em; //@RequiredArgsConstructor 덕분에 자동으로 관계 주입 해줌
public void save(Item item) {
if (item.getId() == null) {
em.persist(item);
} else {
em.merge(item);
}
}
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();
}
public List<Item> findByName(String name) {
return em.createQuery("select i from Item i where i.name = :itemName", Item.class)
.setParameter("itemName", name)
.getResultList();
}
}
@RequiredArgsConstructor 로 생성자를 별도로 생성하지 않아도 자동으로 생성 & final 키워드가 붙어 있는 변수들(private final EntityManager em)에 대해서 생성자 관계 주입을 해줍니다.
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class ItemService {
private final ItemRepository itemRepository;
@Transactional //DEFAULT = false
public void saveItem(Item item) {
itemRepository.save(item);
}
public Item findOne(Long itemId) {
return itemRepository.findOne(itemId);
}
public List<Item> findItems() {
return itemRepository.findAll();
}
public List<Item> findItemsByName(String name) {
return itemRepository.findByName(name);
}
}