It's easy, if you try

[SpringBoot] 스프링부트와 JPA 활용 섹션 5, 6 - 도메인 개발 / JPA 동적 쿼리 (JPQL, JPA Criteria) 본문

스프링

[SpringBoot] 스프링부트와 JPA 활용 섹션 5, 6 - 도메인 개발 / JPA 동적 쿼리 (JPQL, JPA Criteria)

s5he2 2023. 3. 2. 21:22
반응형

주문 도메인 개발 

  1. 주문 엔티티 개발
    •  생성 메서드 및 비즈니스 로직 작성
    • @NoArgsConstructor(access = AccessLevel.PROTECTED) : 외부에서 set 할 수 없도록 하는 옵션
    • 주문 검색 기능 개발 (아래 참고)
  2. 주문 리포지토리 개발
  3. 주문 서비스 개발
    • new Order(); 등의 각각의 set은 지양하고 createOrder 함수를 생성하는것이 유지보수 측면에서 용이하다.
  4. 주문 기능 테스트 
참고 : 주문 서비스의 주문과 주문 취소 메서드를 보면 비즈니스 로직 대부분이 엔티티에 있다. 서비스 계층은 단순히 엔티티에 필요한 요청을 위임하는 역할을 한다. 이처럼 엔티티가 비즈니스 로직을 가지고 객체 지향의 특성을 적극 활용하는 것을 도메인 모델 패턴(https://martinfowler.com/eaaCatalog/domainModel.html)이라 한다. 반대로 엔티티에는 비즈니스 로직이 거의 없고, 서비스 계층에서 대부분의 비즈니스 로직을 처리하는 것을 트랜잭션 스크립트 패턴(https://martinfowler.com/eaaCatalog/transactionScript.html)이라 한다.

주문 검색 기능 개발 (JPA 동적 쿼리)

  1. OrderSearch 클래스 생성
    • 조건인 memberName, orderStatus 속성 생성
  2. 리포지토리에서 검색 함수(findAll) 생성 

1) JPQL로 처리(번거롭고, 버그가 발생할 수 있음)

public List<Order> findAllByString(OrderSearch orderSearch) {
    //language=JPAQL
    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();
}

2) 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();
}
JPA Criteria는 JPA 표준 스펙이지만 실무에서 사용하기에 너무 복잡하다. 결국 다른 대안이 필요하다. 많은 개발자가 비슷한 고민을 했지만, 가장 멋진 해결책은 Querydsl이 제시했다. Querydsl 소개장에서 간단히 언급하겠다. 지금은 이대로 진행하자.

JPA 더티 채킹 

  • orderItem.setStatus(OrderStatus.CANCEL);
  • getItem().addStock(count): 

와 같이 엔티티의 상태나 수량 등 속성이 변하는 함수를 작성하면 JPA가 변화를 감지하고 DB에 update와 같은 쿼리를 직접 전송한다. 

 

 

반응형
Comments