06 주문 도메인 개발 - 주문 검색 기능 개발

shin·2023년 10월 15일
0

1. OrderSearch - 검색 조건 파라미터

package jpabook.jpashop.repository;

import jpabook.jpashop.domain.OrderStatus;
import lombok.Getter;
import lombok.Setter;

@Getter @Setter
public class OrderSearch {

    private String memberName; //회원이름
    private OrderStatus orderStatus; //주문상태[ORDER, CANCEL]

}

2. OrderRepository - 검색 동적 쿼리 작성

  • finalAll(OrderSearch orderSearch) 메서드 : 검색 조건에 동적으로 쿼리를 생성해서 주문 엔티티를 조회

0) 동적 쿼리를 고려하지 않은 방법

    public List<Order> findAll(OrderSearch orderSearch){
        return em.createQuery("select o from Order o join o.member m" +
                " where o.status = :status" +
                " and m.name like :name", Order.class)
                .setParameter("status", orderSearch.getOrderStatus())
                .setParameter("name", orderSearch.getMemberName())
                .setMaxResults(1000) //최대 1000건
                .getResultList();
    }
  • Order랑 Order와 연관된 memberjoin
    • 객체이기 때문에 참조하는 스타일로 조인을 수행함
  • 위 코드로 쿼리를 작성하면 값이 없는 경우에 알맞지 않은 응답을 내보낼 수 있음
    • 따라서 동적 쿼리로 작성을 해야 함

방법 1) JPQL로 처리

    public List<Order> findAll(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);

        if(orderSearch.getOrderStatus() != null){
            query = query.setParameter("status", orderSearch.getOrderStatus());
        }
        if(StringUtils.hasText(orderSearch.getMemberName())){
            query = query.setParameter("name", orderSearch.getMemberName());
        }

        return query.getResultList();
    }
  • jpql을 문자로 생성하는 것은 수동으로 수행을 해야하는 작업이기 때문에, 번거롭고 실수로 인한 버그 발생 가능성이 높은 방식
  • 코드의 길이도 매우 길어짐

방법 2) JPA Criteria로 처리

    /*
        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); //회원과 조인

        // 동적 쿼리에 대한 컨디션 조합을 Predicate을 활용해서 깔끔하게 만들 수 있음
        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();
    }
  • jpql을 자바 코드로 작성할 수 있도록 JPA에서 표준으로 제공하는 방식

  • 동적 쿼리에 대한 컨디션 조합을 Predicate을 활용해서 깔끔하게 만들 수 있음

    • Predicate 자체가 조건이 됨
  • 하지만 치명적인 단점이 존재함

    • 유지보수성이 제로에 가까움
    • 쿼리가 눈에 잘 들어오지 않음

방법 3) Querydsl

  • JPA Criteria는 JPA 표준 스펙이지만 실무에서 사용하기에 너무 복잡함
  • 가장 좋은 해결책은 Querydsl이 제시함
    /*
        Querydsl
     */
    public List<Order> findAll(OrderSearch orderSearch){
        QOrder order = QOrder.order;
        QMember member = QMember.member;

        return query
                .select(order)
                .from(order)
                .join(order.member, member)
                .where(statusEq(orderSearch.getOrderStatus()),
                        nameLike(orderSearch.getMemberName()))
                .limit(1000)
                .fetch();
    }


    private BooleanExpression statusEq(OrderStatus statusCond){
        if(statusCond == null){
            return null;
        }
        return order.status.eq(statusCond);
    }

    private BooleanExpression nameLike(String nameCond){
        if(!StringUtils.hasText(nameCond)){
            return null;
        }
        return member.name.like(nameCond);
    }
  • 동적 쿼리 뿐만 아니라 복잡한 정적 쿼리도 Querydsl로 작성하면 좋음

  • 컴파일 시점에서 오류를 잡아낼 수 있음

  • 위 코드는 완성된 코드는 아니며, 추후 Querydsl에 대한 내용을 추가로 수강하여 코드를 완성할 예정

    • 일단은 방법1, 방법2로 진행


강의 : 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발

profile
Backend development

0개의 댓글