728x90
JPA에서 동적쿼리를 어떻게 해결해야 하는가?
검색 조건 파라미터 OrderSearch
@Getter @Setter
public class OrderSearch {
private String memberName; // 회원 이름
private OrderStatus orderStatus; // 주문 상태(Order, Cancle)
}
주문 리포지토리에 검색 추가
// 주문 조회 -> 검색이 되어야 함
public List<Order> findAll(OrderSearch orderSearch){
return em.createQuery("select o from Order o join o.member" +
"where o.status = :status" +
"and m.name like :name", Order.class)
.setParameter("status", orderSearch.getMemberName())
.setMaxResults(1000) // 최대 1000건
.getResultList();
}
다만, 위의 코드는 값이 전부 있다는 가정을 했을 때의 코드이다.
status가 없거나 name이 없을 때에는
// 주문 조회 -> 검색이 되어야 함
public List<Order> findAll(OrderSearch orderSearch){
return em.createQuery("select o from Order o join o.member", Order.class)
.setMaxResults(1000) // 최대 1000건
.getResultList();
}
이런식으로 리스트 전체가 출력되어야 하는데 이렇게 상황에 따라 쿼리가 달라지는 경우를 동적쿼리라고 한다.
즉, findAll(OrderSearch orderSearch) 메서드는 검색 조건에 동적으로 쿼리를 생성해서 주문 엔티티를 조회한다.
동적쿼리 문제를 해결하기 위해 JPQL을 사용할 수 있다.
JPQL
JPQL이란? Java Persistence Query Language, 객체지향 쿼리로 JPA가 지원하는 다양한 쿼리 방법 중 하나
- SQL은 테이블 중심
- JPQL은 엔티티 중심
SELECT *
FROM orders o
JOIN member m ON o.member_id = m.id
WHERE m.name = 'kim';
String jpql = "SELECT o FROM Order o WHERE o.member.name = :name";
실제로 실행될 때는 JPA가 내부적으로 SQL로 바꿔서 DB에 보내준다!
public List<Order> findAll(OrderSearch orderSearch){
String jpql = "select o from Order o join o.member m";
boolean isFirstCondition = true;
// 주문 상태 검색
if(orderSearch.getOrderStatus() != null){ // 주문상태가 있을 경우(Order, Cancle)
if(isFirstCondition){ // 쿼리에 넣는 첫 조건
jpql += "where";
isFirstCondition = false;
}else{ // 아니면 and로 조건 이어가기
jpql += "and";
}
}
// 회원 이름 검색
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());
}
List<Order> resultList = query.getResultList();
return resultList;
}
JPQL은 객체 지향 쿼리를 지원하지만, 문자열로 직접 조합하는 방식은 번거롭고 오류 발생 가능성이 크다.
특히 조건이 늘어나면서 where, and, setParameter 등을 수동으로 관리해야 하기 때문에 복잡한 동적 쿼리를 다루기엔 한계가 있다.
그럼 어떤 방식이 좋을까?
JPA Criteria로 처리
Criteira란? JPQL을 자바 코드로 작성하도록 도와주는 빌더 클래스 API
// 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<Object, Object> m = o.join("member", JoinType.INNER);
List<Predicate> criteira = new ArrayList<>();
if(orderSearch.getOrderStatus() != null){
Predicate status = cb.equal(o.get("status"), orderSearch.getOrderStatus());
criteira.add(status);
}
if(StringUtils.hasText(orderSearch.getMemberName())){
Predicate name = cb.like(m.get("name"),"%"+orderSearch.getMemberName()+"%");
criteira.add(name);
}
cq.where(cb.and(criteira.toArray(new Predicate[criteira.size()])));
TypedQuery<Order> query = em.createQuery(cq).setMaxResults(1000);
return query.getResultList();
}
JPA Criteria는 JPA 표준 스펙이지만 실무에서 사용하기에 복잡해서 유지보수가 어렵다는 단점이 있다.
그럼 어떻게 해결할 수 있을까?!? 바로 Querydsl !
728x90
'BE > 스프링부트와 JPA 활용' 카테고리의 다른 글
[JPA] 7. 웹 계층 개발 - 홈 화면과 레이아웃 (0) | 2025.05.20 |
---|---|
[JPA] 6. 주문 도메인 개발 - 리포지토리/서비스/테스트 (0) | 2025.05.17 |
[JPA] 6. 주문 도메인 개발 - 주문, 주문상품 엔티티 개발 (1) | 2025.05.14 |
[JPA] 5. 상품 도메인 개발 (0) | 2025.05.14 |
[JPA] 4. 회원 도메인 개발 (0) | 2025.05.13 |