[Spring] Query DSL - 중급 문법

Kyungmin·2024년 4월 8일
0

Spring

목록 보기
9/39

1. 프로젝션(Projection)

  • select 대상을 지정

1️⃣ 프로젝션 대상이 하나

List<String> result = queryFactory
         .select(member.username)
     	 .from(member)
     	 .fetch();
  1. 프로젝션 대상이 하나면 타입을 명확하게 지정할 수 있음
  2. 프로젝션 대상이 둘 이상이면 '튜플' 이나 DTO로 조회

2️⃣ 프로젝션 대상이 둘 이상일 때 사용

1. 튜플(Tuple) 조회

List<Tuple> result = queryFactory
         .select(member.username, member.age)
         .from(member)
         .fetch();
 for (Tuple tuple : result) {
     String username = tuple.get(member.username);
     Integer age = tuple.get(member.age);
     System.out.println("username=" + username);
  
      System.out.println("age=" + age);
 }

2. DTO 조회

  • 순수 JPA 에서 DTO 조회

Member DTO

@Data
 public class MemberDto {
     private String username;
     private int age;
     public MemberDto() {
     }
     public MemberDto(String username, int age) {
         this.username = username;
         this.age = age;
  } 
}
순수 JPA에서 DTO 조회 코드
List<MemberDto> result = em.createQuery(
         "select new study.querydsl.dto.MemberDto(m.username, m.age) " +  
         "from Member m", MemberDto.class)
     .getResultList();
  • 순수 JPA에서 DTO를 조회할 때는 new 명령어를 사용해야함
  • DTO의 package이름을 다 적어줘야해서 지저분함
  • 생성자 방식만 지원함

.
.

Querydsl 빈 생성(Bean population)

  • 결과를 DTO 반환할 때 사용
  • 다음 3가지 방법 지원
  1. 프로퍼티 접근 - setter
  2. 필드 직접 접근
  3. 생성자 사용

1. 프로퍼티 접근 - setter

List<MemberDto> result = queryFactory
         .select(Projections.bean(MemberDto.class,
                 member.username,
                 member.age))
         .from(member)
.fetch();

2. 필드 직접 접근

  • getter, setter가 없어도 바로 필드에 접근(바로 필드에 쏙~!)
  • 만약 MemberDto.class 에 name 과 age 라는 이름을 가진 필드가 있다면 username 과 필드명이 달라 매칭이 안되어 null 이 들어간다.
    이런 경우에는 member.username.as("name") -> name 은 매핑하려는 dto 의 필드명 으로 코드를 작성해주면 정상적으로 매핑이 되어 값이 들어간다.
List<MemberDto> result = queryFactory
         .select(Projections.fields(MemberDto.class,
        member.username,
        member.age))
.from(member)
.fetch();

3. 생성자 사용

List<MemberDto> result = queryFactory
         .select(Projections.constructor(MemberDto.class,
                 member.username,
                 member.age))
         .from(member)
.fetch(); }

2. 동적쿼리

  • 동적쿼리를 해결하는 두가지 방식
  1. BooleanBuilder
  2. Where 다중 파라미터 사용

1. BooleanBuilder

  • username 이나 age 가 둘다 있으면 둘다 값이 들어가고 하나는 null 이고 다른 하나는 값이 있으면 다른 하나만 들어간다.
  • 만약 요구사항으로 category 또는 name 둘 다 null 이 아니고, 둘 중 하나만 보내도 검색이 가능하게 하고 싶을 때 사용할 수 있을 듯!
private List<Member> searchMember1(String usernameCond, Integer ageCond) { 

    BooleanBuilder builder = new BooleanBuilder();
    
    if (usernameCond != null) {
        builder.and(member.username.eq(usernameCond));
    }
    if (ageCond != null) {
        builder.and(member.age.eq(ageCond));
    }
    return queryFactory
		.selectFrom(member)
		.where(builder)		// 조건절에 builder 를 넣어주면 끝
		.fetch();

2. Where 다중 파라미터 사용

private List<Member> searchMember2(String usernameCond, Integer ageCond) {
     return queryFactory
             .selectFrom(member)
             .where(usernameEq(usernameCond), ageEq(ageCond))  // 1과 2 를 사용
             .fetch();
}

// 1
 private BooleanExpression usernameEq(String usernameCond) {
     return usernameCond != null ? member.username.eq(usernameCond) : null;
}


// 2
private BooleanExpression ageEq(Integer ageCond) {
    return ageCond != null ? member.age.eq(ageCond) : null;
}
  • where 조건에 null 값은 무시된다.
  • 메서드를 다른 쿼리에서도 재활용 할 수 있다.
  • 쿼리 자체의 가독성 이 높아진다.

조립(조합) 가능 - 자바 코드이기 때문에

  • null 체크는 주의해서 처리해야함
  • BooleanExpression 으로 사용할 시 조립(조합) 이 가능 ( 메서드1+메소드2 가능 )
 private BooleanExpression allEq(String usernameCond, Integer ageCond) {
     return usernameEq(usernameCond).and(ageEq(ageCond));
}

3. 벌크(Bulk) 연산

  • 쿼리 한번으로 대량의 데이터를 수정할 때 사용
  • 예를 들어 어느 회사의 개발자들의 연봉을 10% 인상시켜야 한다면 쿼리 한번으로 끝내는게 좋다. -> 벌크(Bulk) 연산
'28살 미만' 인 사람들의 이름을 모두 '비회원'으로 변경하는 코드
long count = queryFactory
         .update(member)
.set(member.username, "비회원") .where(member.age.lt(28)) .execute();

주의: JPQL 배치와 마찬가지로, 영속성 컨텍스트에 있는 엔티티를 무시하고 실행되기 때문에 배치 쿼리를 실행하 고 나면 영속성 컨텍스트를 초기화 하는 것이 안전하다.

  • 영속성 컨텍스트가 DB에 있는 데이터보다 우선권을 가진다.
  • bulk 연산이 나간다는건 DB랑 영속성 컨텍스트랑 안맞기 때문에 초기화하는 것이 낫다.
  • 따라서 위의 말처럼 영속성 컨텍스트를 초기화(em.flush(), em.clear())를 하고 가져오자.

산술 연산

  • 기존 숫자에 1 더하기
long count = queryFactory
         .update(member)
         .set(member.age, member.age.add(1))
         .execute();
  • 쿼리 한번으로 대량 데이터 삭제
 long count = queryFactory
         .delete(member)
    	 .where(member.age.gt(18))
    	 .execute();
profile
Backend Developer

0개의 댓글

관련 채용 정보