[Spring_Boot] JPA SHOP - Order( 예외처리 만들기 & 가변인자 & CasecadeType & queryDSL)

최현석·2022년 12월 14일
0

Spring_Boot

목록 보기
29/31

🧩 존재하지 않는 예외처리 만들기

Item(Domain)

if(restStock < 0) {
			// exception
			throw new NotEnoughStockException("need more stock");
		}
  • 패키지 생성 -> 클래스 생성
  • 클래스 안에서 superclass 생성
  • 두번째 항목 제외 모두 선택 후 exception 생성

NotEnoughStockException

public class NotEnoughStockException extends RuntimeException{

	public NotEnoughStockException() {
		super();
	}

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

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

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

🧩 파라미터 가변인자

  • 파라미터 가변인자

  • 자바에서는 파라미터 개수가 다르면 다른 메소드로 인식 한다.

  • 파라미터가 한 개인 경우
    public void search(String one){}

  • Map을 사용하는 경우
    public void search(Map<String, String> param){}

  • List를 사용하는 경우
    public void search(List param){}

  • VO객체를 사용하는 경우
    public void search(ParamVO param){}

  • [가변인자]

    • 가변인자를 사용하면 동적으로 파라미터를 받을 수 있다.
    • 사용법은 변수 타입 뒤에 기호(...)를 붙여주면 된다.
    • 가변인자를 가지고 있는 메소드를 호출할 떄의 방법
public class Main01 {

	public static void main(String[] args) {
		test();
		test("a");
		test(1,"a");
		test("a","b");
		test(5, new String[] {"a","b","c"});
	 	test(1, true, "홍길동", "이순신", "유성룡");
	}
  
	public static void test(String... param) {
		System.out.println("test1번 실행");
		
		String[] array = param;
		for(String str:param) {
			System.out.println(str);
		}
		System.out.println("----------------------");
	}

	// 다른 파라미터와 가변인자를 같이 사용하는 경우에는 가변인자를 제일 뒤에 위치시켜야 한다.
 	// int num 변수가 반드시 있어야 한다.
	public static void test(int num, boolean bool, String... param) {
		System.out.println("test2번 실행");
		System.out.println("num : " +num);
  		System.out.println("bool : " +bool);

  		String[] array = param;
		for(String str:param) {
			System.out.println(str);
		}
		System.out.println("----------------------");
	}
}
  • 결과

🧩 CasecadeType.ALL

  • 영속성 전이

  • 특정 엔티티에 대해 특정한 작업을 수행하면 관련된 엔티티에도 동일한 작업을 수행한다는 의미

  • 아래의 예시를 봅시다. 현재 Parent와 Child는 1:N 연관관계 (@OneToMany, @ManyToOne 사용)를 가진 상태입니다.

  • 위의 코드대로라면 persist 상태로 세 객체를 만들기 위해서 persist()를 세 번 호출해야 합니다.

  • 이런 것이 번거롭기 때문에 우리는 parent객체 하나만으로 child1, child2를 같이 관리하고 싶은 겁니다.

  • parent하나만 persist 상태로 만들면 이것과 연관관계를 가진 child1, child2가 자동으로 persist가 되도록요.

이때 사용하는 것이 바로 CASCADE 입니다.

  • 이 상태에서 parent만 persist를 하면 child1, child2가 같이 persist가 됩니다.

🧩 queryDSL 사용 세팅

buil.gradle

  • 세팅 후 buil.gradle -> gradle -> Refresh Gradle Project
buildscript {
   dependencies {
      classpath("gradle.plugin.com.ewerk.gradle.plugins:querydsl-plugin:1.0.10")
   }
}
  
apply plugin: "com.ewerk.gradle.plugins.querydsl"

dependencies {
	implementation 'com.querydsl:querydsl-jpa'
	implementation 'com.querydsl:querydsl-apt'
	
}
  

//querydsl 추가 
def querydslDir = 'src/main/generated' 
//def querydslDir = "$buildDir/generated/querydsl" 
querydsl { 
	library = "com.querydsl:querydsl-apt" 
	jpa = true 
	querydslSourcesDir = querydslDir 
} 

sourceSets { 
	main { 
		java { 
			srcDirs = ['src/main/java', querydslDir] 
		} 
	} 
} 

compileQuerydsl{ 
	options.annotationProcessorPath = configurations.querydsl 
}
 
configurations {
	querydsl.extendsFrom compileClasspath 
} 
  • 아래와 같이 클릭


  • Gradle Tasks에서 프로젝트에 해당하는 폴더 클릭

  • build 부분 클릭 후 Run Gradle Tasks 클릭

  • 성공, build.gradle -> gradle -> Refresh Gradle Project 를 한번 더 실행해준다.

  • 경로에 맞춰서 폴더가 생성된다.




OrderStatus

  • 관리가 편하다
public enum OrderStatus {
	ORDER, CANCEL
}

OrderSearch

@Getter @Setter
public class OrderSearch {

	private String memberName;
	private OrderStatus orderStatus;
}

🧩Domain

CasecadeType.ALL

  • 영속성 전이
  • 특정 엔티티에 대해 특정한 작업을 수행하면 관련된 엔티티에도 동일한 작업을 수행한다는 의미

Order

@Entity
@Getter @Setter
@Table(name = "orders")
public class Order {

	@Id @GeneratedValue
	@Column(name="order_id")
	private Long id;
	
	@ManyToOne
	@JoinColumn(name = "member_id")
	private Member member;
	private LocalDateTime orderDate;
	
	@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
	private List<OrderItem> orderItems
		= new ArrayList<OrderItem>();
	
	// 주문상태(ORDER, CANCEL) -> Enum
	@Enumerated(EnumType.STRING)
	private OrderStatus orderStatus;
	
	//////// 연관관계 메서드
	public void setMember(Member member) {
		this.member = member;
		member.getOrders().add(this);
		System.out.println(this);
	}
	
	public void addOrderItem(OrderItem orderItem) {
		orderItems.add(orderItem);
		orderItem.setOrder(this);
	}
	//////// 연관관계 메서드
	
	public static Order createOrder(Member member, OrderItem... orderItems) {
		Order order = new Order();
		order.setMember(member);
		
		for(OrderItem orderItem : orderItems) {
			order.addOrderItem(orderItem);
		}
  
		// order 시간, 상태 order table만 가지고 있는 내용 이므로
		// 따로 set을 해준다.
		order.setOrderStatus(OrderStatus.ORDER);
		order.setOrderDate(LocalDateTime.now());
		
		return order;
	}

	// 주문 취소
	public void cancel() {
		this.setOrderStatus(orderStatus.CANCEL);
  		// 하나의 오더에 주문별로 오더 아이템부분에 cancel을 날려야 할 수 있어서 반복문 처리
		for(OrderItem orderItem : orderItems) {
			orderItem.cancel();
		}
	}
	
}

OrderItem

@Entity
@Getter @Setter
public class OrderItem {

	@Id @GeneratedValue
	@Column(name = "order_item_id")
	private Long id;
	
	@ManyToOne
	@JoinColumn(name = "order_id")
	private Order order;
	
	@ManyToOne
	@JoinColumn(name = "item_id")
	private Item item;
	
	private int orderPrice; 	// 주문가격
	private int count;		// 주문수량
	
	
	// 생성 메서드
	public static OrderItem createOrderItem(Item item, int orderPrice, int count) {
		OrderItem orderItem = new OrderItem();
		orderItem.setItem(item);
		orderItem.setOrderPrice(orderPrice);
		orderItem.setCount(count);
		
		// 주문한 만큼 재고 조정
        // Item에 있는 stockQuantity 재고 조정
		item.removeStock(count);
		return orderItem;
		
	}

	// 취소
	public void cancel() {
		//재고수량 원복
		getItem().addStock(count);
	}
	
}

🧩DTO

OderSearch

@Getter @Setter
public class OrderSearch {

	private String memberName;
	private OrderStatus orderStatus;
}

🧩Web

orderForm

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments/header :: header" />
<body>

	<div class="container">
		<div th:replace="fragments/bodyHeader :: bodyHeader" />

		<form role="form"  action="/order" method="post">
			<div class="form-group">
				<label for="member">주문회원</label> 
				<select name="memberId" id="member" class="form-control">
					  	<option value="">회원선택</option>
					  	<option th:each="member : ${members}"
					  		th:value="${member.id}" th:text="${member.name}"/>
				</select>
			</div>

			<div class="form-group">
				<label for="item">상품명</label> 
				<select name="itemId" id="item" class="form-control">
						<option value="">상품선택</option>
					  	<option th:each="item : ${items}"
					  		th:value="${item.id}" th:text="${item.name}"/>
				</select>
			</div>

			<div class="form-group">
				<label for="count">주문수량</label> 
				<input type="number" name="count" class="form-control" id="count" placeholder="주문 수량을 입력하세요">
			</div>

			<button type="submit" class="btn btn-primary">Submit</button>
		</form>
		<br />
		<div th:replace="fragments/footer :: footer" />

	</div>
	<!-- /container -->

</body>
</html>

orderList

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments/header :: header" />
<body>
	<div class="container">
		<div th:replace="fragments/bodyHeader :: bodyHeader" />
		<div>
			<div>
				<form th:object="${orderSearch}" class="form-inline">
					<div class="form-group mb-2">
						<input type="text" th:field="*{memberName}" class="form-control" placeholder="회원명" />
					</div>
					<div class="form-group mx-sm-1 mb-2">
						<select th:field="*{orderStatus}" class="form-control">
							<option value="">주문상태</option>
							<option
								th:each="status : ${T(com.koreait.jpashop.domain.OrderStatus).values()}"
								th:value="${status}" th:text="${status}">option</option>
						</select>
					</div>
					<button type="submit" class="btn btn-primary mb-2">검색</button>
				</form>
			</div>

			<table class="table table-striped">
				<thead>
					<tr>
						<th>#</th>
						<th>회원명</th>
						<th>대표상품 이름</th>
						<th>대표상품 주문가격</th>
						<th>대표상품 주문수량</th>
						<th>상태</th>
						<th>일시</th>
						<th></th>
					</tr>
				</thead>
				<tbody>
					<tr th:each="item : ${orders}">
						<td th:text="${item.id}"></td>
						<td th:text="${item.member.name}"></td>
						<td th:text="${item.orderItems[0].item.name}"></td>
						<td th:text="${item.orderItems[0].orderPrice}"></td>
						<td th:text="${item.orderItems[0].count}"></td>
						<td th:text="${item.orderStatus}"></td>
						<td th:text="${item.orderDate}"></td>
						<td><a th:if="${item.orderStatus.name() == 'ORDER'}" href="#"
							th:href="'javascript:cancel('+${item.id}+')'"
							class="btn btn-danger">CANCEL</a>
						</td>
					</tr>
				</tbody>
			</table>
		</div>

		<div th:replace="fragments/footer :: footer" />

	</div>
	<!-- /container -->

</body>
<script>
  	<!-- 취소 기능 -->
	function cancel(id){
		let form = document.createElement("form");
		form.setAttribute("method","post");
		form.setAttribute("action","/orders/" + id + "/cancel");
		document.body.appendChild(form);
		form.submit();
	}
</script>
</html>

🧩Controller

OrderController

@Controller
@RequiredArgsConstructor
public class OrderController {
	
    private final OrderService orderService;
    private final MemberService memberService;
    private final ItemService itemService;
    
	// request : order
	// member 조회, item 조회
	// response : order/orderForm
    @GetMapping("/order")
	public String createForm(Model model) { 
    	// member 조회
		List<Member> members = memberService.findMembers();
		// item 조회
		List<Item> items = itemService.findItems();

		model.addAttribute("members",members);
		model.addAttribute("items",items);
        return "order/orderForm";
	}
	
    @PostMapping("/order")
    public String order( @RequestParam("memberId") Long memberId,
    					 @RequestParam("itemId") Long itemId,	
    					 @RequestParam("count") int count	) {
    	
    	orderService.order(memberId, itemId, count);
		return "redirect:/orders";
    }
    
    @GetMapping("/orders")
    public String orderList(@ModelAttribute("orderSearch")OrderSearch orderSearch, Model model) {
    	// 조회
        List<Order> orders = orderService.findOrders(orderSearch);
        model.addAttribute("orders", orders);
    	return "order/orderList";
    }
    
    // 취소
    @PostMapping("/orders/{orderId}/cancel")
    public String cancelOrder(@PathVariable("orderId")Long orderId) {
    	orderService.cancelOrder(orderId);
    	return "redirect:/orders";
    }
	
}

🧩Service

OrderSerivce

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

	private final OrderRepository orderRepository;
	private final MemberRepository memberRepository;
	private final ItemRepository itemRepository;
	
	// 주문
	@Transactional
	public Long order(Long memberId, Long itemid, int count) {
		// 엔티티 조회
		// jpa 영속성 컨텍스트 영역의 들어 옴
		Member member = memberRepository.findOne(memberId);
		Item item = itemRepository.findOne(itemid);
		
		// 주문상품 생성
		OrderItem orderItem = OrderItem.createOrderItem(item, item.getPrice(), count);
		
		// 주문 생성
        // 어떤 사람이 어떤 오더 아이템을 준비 했는지 
		Order order = Order.createOrder(member,orderItem);
		
		// 주문 저장
		orderRepository.save(order);
		
		return order.getId();		
				
				
	}

	public List<Order> findOrders(OrderSearch orderSearch) {
		return orderRepository.findAll(orderSearch);
	}
	
	// 취소
	@Transactional
	public void cancelOrder(Long orderId) {
		Order order = orderRepository.findOne(orderId);
		// 취소(update)에 대한 비지니스 로직 처리
		order.cancel();
	}
	
	
}

🧩Repository

QueryDSL

  • QClass 세팅

OrderRepository

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

    // order 저장
	public void save(Order order) {
		em.persist(order);
	
	}
	
	// 단건 조회
	public Order findOne(Long id) {
		return em.find(Order.class, id);
	}
	
	// 조회, queryDSL
	public List<Order> findAll(OrderSearch orderSearch) {
		JPAQueryFactory query = new JPAQueryFactory(em);
		QOrder order = QOrder.order;
		QMember member = QMember.member;
		
		
		return query.select(order)
					.from(order)
					.join(order.member, member) // member : member의 알리아스
//					.where(order.orderStatus.eq(orderSearch.getOrderStatus())
//							,member.name.like(orderSearch.getMemberName()))
					.where(statusEq(orderSearch.getOrderStatus()),nameLike(orderSearch.getMemberName()))
					.fetch()
					;
	}

    private BooleanExpression statusEq(OrderStatus orderStatus) {
    	if( orderStatus == null) {
    		return null;
    	}
    	return QOrder.order.orderStatus.eq(orderStatus);
    }
    
    private BooleanExpression nameLike(String memberName) {
    	if(memberName == null || memberName.equals("")) {
    		return null;
    	}
    	return QMember.member.name.like(memberName);
    }

  
}

0개의 댓글