자바 ORM 표준 JPA 프로그래밍 스터디 - 5주차 (QueryDSL)

큰모래·2023년 6월 4일
0

10.4 QueryDSL

10.4.1 시작


기본 Q 생성

  • 쿼리 타입(Q)은 사용하기 편리하도록 기본 인스턴스를 보관하고 있다.
  • 같은 엔티티를 조인하거나 같은 엔티티를 서브쿼리에 사용할때는 별칭을 직접 지정해서 사용해야 한다.
    QMember qMember = new QMember("m"); //직접 지정
    QMember qMember = QMember.member;   //기본 인스턴스 사용
    • 위에 소스를 기본 인스턴스를 사용하면 간결하게 작성이 가능

      import static jpabook.jpashow.doamin.QMember.member; //기본 인스턴스
      
      public void basic() {
      		EntityManager em = emf.createEntityManager();
      
      		JPAQuery query = new JPAQuery(em);
      		List<Member> members =
      				query.from(member)
      						.where(member.name.eq("회원"))
      						.orderBy(member.name.desc())
      						.list(member); // fetch
      }

10.4.2 검색 조건 쿼리


  • QueryDSL의 where절에는 and, or 을 사용할 수 있다.
  • where(item.name.eq(”좋은상품”), item.price.gt(20000)) 처럼 and 연산을사용해도 됨.
//QueryDSL 기본 쿼리 기능
JPAQuery query = new JPAQuery(em);
QItem item = QItem.item;
List<Item> list = query.from(item)
				.where(item.name.eq("좋은상품").and(item.price.gt(20000)))
				.list(item);

//실행된 JPQL
select item
from Item item
where item.name = ?1 and item.price >?2

//where 메서드에서 사용가능한 메서드
item.price.between(10000, 20000); //가격이 10000원 ~ 20000원 상품
item.name.contains("상품1"); //상품1이라는 이름을 포함한 상품 (like '%상품1%')
item.name.startWith("고급"); //이름이 고급으로 시작하는 상품 (like '고급%')

10.4.3 결과 조회


  • uniqueResult()
    • 조회 결과가 한 건일 때 사용한다.
  • singleResult()
    • uniqueResult()와 같지만 결과가 하나 이상이면 처음 데이터를 반환한다.
  • list()
    • 결과가 하나 이상일 때 사용한다. 결과가 없으면 빈 컬렉션을 반환한다.

10.4.4 페이징과 정렬


  • 정렬은 orderBy를 사용하는데 쿼리 타입이 제공하는 asc, desc를 사용한다.
  • 페이징은 offset 과 limit을 적절히 조합해서 사용하면 된다
QItem item = QItem.item;

//기본 사용법
query.from(item)
		.where(item.price.gt(20000))
		.orderBy(item.price.desc(), item.stockQuantity.asc())
		.offset(10).limit(20)
		.list(item);

//restrict 사용
QueryModifiers queryModifiers = new QueryModifiers(20L, 10L); //limit, offset
List<Item> list = query.from(item)
		.restrict(queryModifiers)
		.list(item);

//실제 페이징 처리 -> 전체 데이터 수를 알아야 하므로 listResults()를 사용해야 한다.
SearchResults<Item> result =
		query.from(item) 
		.where(item.price.gt(20000))
		.offset(10).limit(20) 
		.listResults(item)   //지금은 fetch()로 바뀜.

10.4.5 그룹


groupBy를 사용하고, 그룹화된 결과에 제한을 걸려면 having을 사용한다.

query.from(item)
		.groupBy(item.price)
		.having(item.price.gt(1000))
		.list(item);

10.4.6 조인


  • 조인은 첫번째 파라미터에 조인 대상을 지정, 두번째 파라미터에 별칭으로 사용할 쿼리 타입을 지정한다.

QOrder order = QOrder.order;
QMember member = QMember.member;
QOrderItem orderItem = QOrderItem.orderItem;

//기본 조인
query.from(order)
		.join(order.member, member)
		.leftJoin(order.orderItems, orderItem)
		.list(order);

//조인 on 사용
query.from(order)
		.leftJoin(order.orderItems, orderItem)
		.on(orderItem.count.gt(2))
		.list(order);

//페치 조인 사용 방법
query.from(order)
		.innerJoin(order.member, member).fetch()
		.leftJoin(order.orderItems, orderItem).fetch()
		.list(order);

//세타 조인 방법 - from 절에 여러 조인을 사용하는 방식
query.from(order, member)
		.where(order.member.eq(member))
		.list(order);

10.4.7 서브 쿼리


  • 서브 쿼리의 결과가 하나면 unique()
  • 서브 쿼리의 결과가 여러 건이면 list()

QItem item = QItem.item;
QItem itemSub = new QItem("itemSub");

//서브쿼리 - 한건
query.from(item)
		.where(item.price.eq(
				new JPASubQuery().from(itemSub).unique(itemSub.price.max())
		))
		.list(item);

//서브쿼리 - 여러건
query.from(item)
		.where(item.in(
				new JPASubQuery().from(itemSub)
						.where(item.name.eq(itemSub.name))
						.list(itemSub)
		))
		.list(item);

10.4.8 프로젝션과 결과 반환


select 절에 조회 대상을 지정하는 것을 프로젝션이라고 한다.

프로젝션 대상이 하나

  • 해당 타입(String)으로 반환된다.
QItem item = QItem.item;
List<String> result = query.from(item).list(item.name);

여러 컬럼 반환과 튜플

  • 프로젝션 대상으로 여러 필드를 선택하면 QueryDSL은 기본으로 Tuple 타입을 사용한다.
  • tuple.get()을 통해 조회 대상을 지정하면 된다.
QItem item = QItem.item;
List<Tuple> result = query.from(item).list(item.name, item.price);

for (Tuple tuple : result){
    System.out.println("name = " + tuple.get(member.name));
    System.out.println("age = " + tuple.get(member.age));
}

빈 생성

  • 쿼리 결과를 엔티티가 아닌 특정 객체로 받고 싶을 때는 빈 생성 기능을 사용한다.
  • 원하는 방법을 지정하기 위해 Projection을 사용하면 된다.
//ItemDTO 생성
public class ItemDTO {
		private String username;
		private int price;

		public ItemDTO() {}
		public ItemDTO(String username, int price) {
				this.username = username;
				this.price = price;
		}

		//Getter, Setter
		...
}

프로퍼티 접근

  • Projections.bean() 메서드를 통해 값을 채운다.
  • ItemDTO의 username 필드와 Item의 name 필드를 매핑시키기 위해 as를 사용한다.

QItem item = QItem.item;
List<ItemDTO> result = query.from(item).list(
		Projections.bean(ItemDTO.class, item.name.as("username"), item.price));

필드 직접 접근

  • Projections.fields()를 사용하면 필드에 직접 접근해서 값을 채워준다.
  • 필드를 private으로 설정해도 동작한다.
QItem item = QItem.item;
List<ItemDTO> result = query.from(item).list(
		Projections.fields(ItemDTO.class, item.name.as("username"), item.price));

생성자 사용

  • 지정한 프로젝션과 파라미터 순서가 같은 생성자가 필요하다.
QItem item = QItem.item;
List<ItemDTO> result = query.from(item).list(
		Projections.constructor(ItemDTO.class, item.name.as("username"), item.price));

10.4.9 수정, 삭제 배치 커리


수정 배치 쿼리

QItem item = QItem.item;

JPAUpdateClause updateClause = new JPAUpdateClause(em, item);
long count = updateClause.where(item.name.eq("jpa 책"))
		.set(item.price, item.price.add(100))
		.execute();

삭제 배치 쿼리

QItem item = QItem.item;

JPADeleteClause deleteClause = new JPADeleteClause(em, item);
long count = deleteClause.where(item.name.eq("j

10.4.10 동적 쿼리


  • BooleanBuilder를 사용하면 특정 조건에 따른 동적 쿼리를 편리하게 생성할 수 있다.
SearchParam param = new  SearchParam();
param.setName("시골개발자");
param.setPrice(10000);

QItem item = QItem.item;

BooleanBuilder builder = new BooleanBuilder();
//상품 이름에 따른 동적인 쿼리
if (StringUtils.hasText(param.getName())) {
		builder.and(item.name.contains(param.getName()));
}
//상품 가격에 따른 동적인 쿼리
if (param.getPrice() != null) {
		builder.and(item.price.gt(param.getPrice()));
}

List<Item> result = query.from(item)
		.where(builder)
		.list(item);

10.4.11 메소드 위임


  • 메소드 위임 기능을 사용하면 쿼리 타입에 검색 조건을 직접 정의할 수 있다.
  • 첫번째 파라미터에 대상 엔티티의 Q타입 인스턴스를 지정, 나머지는 필요한 파라미터를 지정한다.
//검색 조건 정의
public class ItemExpression {
		@QueryDelegate
		public static BooleanExpression isExpensive(QItem item, Integer price) {
				return item.price.gt(price);
		}
}

//쿼리 타입에 생성된 결과
public class QItem extends EntityPathBase<Item> {
		...
		public BooleanExpression isExpensive(Integer price) {
        return ItemExpression.isExpensive(this, price);
    }
}
profile
큰모래

0개의 댓글