JPA활용 도메인
회원 도메인 개발
구현 기능
- 회원 등록
- 회원 목록 조회
회원 리포지토리 개발
@Repository
public class MemberRepository{
@persistenceContext
private EntityManger em;
public void save(Member member){
em.persist(member)
}
public Member findOne(Long id){
return em.find(Member.class, id);
}
public List<Member> findAll(){
return em.createQuery("select m from Member m", Member.class)
.getResultlist;
}
public List<Member> findByName(String name){
return em.createQuery("select m from Member m where m.name = :name", Member.class)
.setParameter("name",name)
.getResultList();
}
}
회원 서비스 코드
@Service
@Transactional(readOnly = true)
@requiredArgsConstructor
public class MemberService{
private final MemberRepository memberRepository;
@Transactional
public Long join(Member member){
validateDuplicateMember(member);
memberRepository.save(member);
return member.getId();
}
private void validateDuplicateMember(Member member){
List<Member> findMembers= memberRepository.findByName(member.getName());
if(!findMembers.isEmpty()){
throw new IllegalStateExeption("이미 존재하는 회원");
}
}
public List<Member> findMembers(){
return memberRepository.findAll();
}
public Member findOne(Long memberId){
return memberRepository.findOne(memberId);
}
}
회원 기능 테스트
@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
public class MemberServiceTest{
@Autowired
MemberService memberService;
@Autowired
MemberRepository memberRepository;
@Test
public void 회원가입() throws Exception{
Member member = new Member();
member.setName("Kim");
Long saveId = memberService.join(member);
assertEquals(member, memberRepository.findOne(saveId));
}
@Test(expected = IllgalStateException.class)
public void 중복_회원_예외() throws Exception{
Member member1 = new Member();
member1.setName("kim");
Member member2 = new Member();
member2.setName("kim");
memberService.join(member1);
memberService.join(member2);
fail("예외발생");
}
}
상품 도메인 개발
//==상품 엔티티에 비즈니스 로직 추가==//
public void addStock(int quantity) {
this.stockQuantity += quantity;
}
public void removeStock(int quantity) {
int restStock = this.stockQuantity - quantity;
if (restStock < 0) {
throw new NotEnoughStockException("need more stock");
}
this.stockQuantity = restStock;
}
//예외 추가 클래스//
public class NotEnoughStockException extends RuntimeException {
public NotEnoughStockException() {
}
public NotEnoughStockException(String message) {
super(message);
}
public NotEnoughStockException(String message, Throwable cause) {
super(message, cause);
}
public NotEnoughStockException(Throwable cause) {
super(cause);
}
}
상품 리포지토리 코드
@Repository
@RequiredConstructor
public class ItemRepository{
private final EntityManager em;
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();
}
}
상품 서비스 코드
@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){
retrun itemRepository.findOne(itemId);
}
}
주문 도메인
// 주문 엔티티 생성 메서드 추가 //
public static Order createOrder(Member member, Delivery delivery, OrderItem...orderItems){
Order order = new Order();
order.setMember(member);
order.setDelivery(delivery);
for (OrderItem orderItem : orderItems){
order.addOrderItem(orderItem);
}
order.setStatus(OrderStatus.ORDER);
order.setOrderDate(LocalDateTime.now());
return order;
}
//비즈니스 로직 추가//
public void cancel(){
if(delivery.getStatus()== DeliveryStatus.COMP){
throw new IllgalStateException("이미 배송된 상품은 취소 불가능");
}
this.setStatus(OrderStatus.CANCEL);
for(OrderItem orderItem : orderItems){
orderItem.cancel();
}
}
public int getTotalPrice(){
int totalPrice = 0;
for(Orderitem orderItem : orderItems){
totalprice += orderItem.getTotalPrice();
}
return totalPrice;
}
//주문상품 생성 메서드 추가 //
public static OrderItem createOrderItem(Item item, int orderPrice, int
count) {
OrderItem orderItem = new OrderItem();
orderItem.setItem(item);
orderItem.setOrderPrice(orderPrice);
orderItem.setCount(count);
item.removeStock(count);
return orderItem;
}
//비즈니스 로직 추가//
public void cancel(){
getItem().addStock(count);
}
public int getTotalPrice(){
return getOrderPrice()*getCount();
}
주문 리포지토리 개발
@Repository
@RequiredArgsConstructor
public class OrderRepository{
private final EntityManger em;
public void save(Order order){
em.persist(order);
}
public Order findOne(Long id){
return em.find(Order.class, id);
}
//JPQL//
public List<Order> findAllByString(OrderSearch orderSearch) {
String jpql = "select o From Order o join o.member m";
boolean isFirstCondition = true;
//주문 상태 검색
if (orderSearch.getOrderStatus() != null) {
if (isFirstCondition) {
jpql += " where";
isFirstCondition = false;
} else {
jpql += " and";
}
jpql += " o.status = :status";
}
//회원 이름 검색
if (StringUtils.hasText(orderSearch.getMemberName())) {
if (isFirstCondition) {
jpql += " where";
isFirstCondition = false;
} else {
jpql += " and";
}
jpql += " m.name like :name";
}
TypedQuery<Order> query = em.createQuery(jpql, Order.class) .setMaxResults(1000); //최대 1000건
if (orderSearch.getOrderStatus() != null) {
query = query.setParameter("status", orderSearch.getOrderStatus());
}
if (StringUtils.hasText(orderSearch.getMemberName())) {
query = query.setParameter("name", orderSearch.getMemberName());
}
return query.getResultList();
}
//JPA Criteria//
public List<Order> findAllByCriteria(OrderSearch orderSearch) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Order> cq = cb.createQuery(Order.class);
Root<Order> o = cq.from(Order.class);
Join<Order, Member> m = o.join("member", JoinType.INNER); //회원과 조인
List<Predicate> criteria = new ArrayList<>();
//주문 상태 검색
if (orderSearch.getOrderStatus() != null) {
Predicate status = cb.equal(o.get("status"),
orderSearch.getOrderStatus());
criteria.add(status);
}
//회원 이름 검색
if (StringUtils.hasText(orderSearch.getMemberName())) {
Predicate name =
cb.like(m.<String>get("name"), "%" +
orderSearch.getMemberName() + "%");
criteria.add(name);
}
cq.where(cb.and(criteria.toArray(new Predicate[criteria.size()])));
TypedQuery<Order> query = em.createQuery(cq).setMaxResults(1000); //최대 1000건
return query.getResultList();
}
// 검색 조건 파라미터//
public class OrderSearch{
private String memberName;
private OrderStatus orderStatus;
}
}
주문 서비스 개발
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class OrderService{
private final MemberRepository memberRepository;
private final OrderRepository orderRepository;
private final ItemRepository itemRepository;
@Transactional
public Long order(Long memberId, Long ItemId, int count){
Member member = memberRepository.findOne(memberId);
Item item = itemRepository.findOne(itemId);
Delivery delivery = new Delivery();
delivery.setAddress(member.getAddress());
delivery.setStatus(DeliveryStatus.READY);
OrderItem orderItem = OrderItem.createOrderItem(item, item.getPrice(),int count);
Order order = Order.createOrder(member, delivery, orderItem);
orderRepository.save(order)
return order.getId();
}
@Transactional
public void cancelOrder(Long orderId){
Order order = orderRepository.findOne(orderId);
order.cancel();
}
public List<Order> findOrders(OrderSearch orderSearch){
return orderRepository.findAll(orderSearch);
}
}
주문 기능 테스트
@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
public class OrderServiceTest {
@PersistenceContext
EntityManager em;
@Autowired OrderService orderService;
@Autowired OrderRepository orderRepository;
@Test
public void 상품주문() throws Exception{
Member member = createMember();
Item item = createBook("JPA",10000,10);
int orderCount = 2;
Long orderId = orderService.order(member.getId(), item.getId(),orderCount);
Order getOrder = orderRepository.findOne(orderId);
assertEquals("상품 주문시 상태는 ORDER",OrderStatus.ORDER,
getOrder.getStatus());
assertEquals("주문한 상품 종류 수가 정확해야 한다.",1,
getOrder.getOrderItems().size());
assertEquals("주문 가격은 가격 * 수량이다.", 10000 * 2,
getOrder.getTotalPrice());
assertEquals("주문 수량만큼 재고가 줄어야 한다.",8, item.getStockQuantity());
}
@Test(expected = NotEnoughStockException.class) public void 상품주문_재고수량초과() throws Exception {
//Given
Member member = createMember();
Item item = createBook("시골 JPA", 10000, 10); //이름, 가격, 재고
int orderCount = 11; //재고보다 많은 수량 //When
orderService.order(member.getId(), item.getId(), orderCount);
//Then
fail("재고 수량 부족 예외가 발생해야 한다."); }
@Test
public void 주문취소() {
//Given
Member member = createMember();
Item item = createBook("시골 JPA", 10000, 10); //이름, 가격, 재고
int orderCount = 2;
Long orderId = orderService.order(member.getId(), item.getId(),
orderCount);
//When
orderService.cancelOrder(orderId);
//Then
Order getOrder = orderRepository.findOne(orderId); assertEquals("주문 취소시 상태는 CANCEL 이다.",OrderStatus.CANCEL,
getOrder.getStatus());
assertEquals("주문이 취소된 상품은 그만큼 재고가 증가해야 한다.", 10,
item.getStockQuantity());
}
private Member createMember() {
Member member = new Member();
member.setName("회원1");
member.setAddress(new Address("서울", "강가", "123-123")); em.persist(member);
return member;
}
private Book createBook(String name, int price, int stockQuantity) {
Book book = new Book();
book.setName(name);
book.setStockQuantity(stockQuantity);
book.setPrice(price);
em.persist(book);
return book;
} }
댓글남기기