빙응의 공부 블로그

[Spring]QueryDSL 중급 문법 본문

Spring/QueryDSL

[Spring]QueryDSL 중급 문법

빙응이 2024. 7. 29. 19:10

📝프로젝션

프로젝션이란 SELECT 대상을 지정하는 것을 말한다. 예를 들어보자

List<String> result = queryFactory
        .select(member.username)
        .from(member)
        .fetch();

해당 코드는 SELECT 대상이 하나이므로 String이라는 명확한 타입으로 지정할 수 있으나

만약 2개 이상이라면 튜플이나 DTO로 조회를 해야한다.

 

튜플 조회
 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);
 }

 

 

📌 DTO 조회

순수 JPA에서 DTO 조회 
  • 순수 JPA는 DTO를 조회할 때 new 명령어를 사용해서 지정한다.
  • 다만 DTO의 경로를 다 적어줘야 하기에 매우 지저분해진다.
 List<MemberDto> result = em.createQuery(
 "select new study.querydsl.dto.MemberDto(m.username, m.age) " +
 "from Member m", MemberDto.class)
        .getResultList();
QueryDSL 빈 생성
  • 결과를 DTO로 반환할 때 사용한다.
  • 다음 3가지 방법을 지원한다.

프로퍼티 접근 - Setter

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

 

필드 직접 접근

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

 

생성자 사용

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

 

🚩별칭이 다를 때 사용

 @Data
 public class UserDto {
 private String name;
 private int age;
 }
 ```
 ```java
 List<UserDto> fetch = queryFactory
        .select(Projections.fields(UserDto.class,
                member.username.as("name"),
                ExpressionUtils.as(
                JPAExpressions
                       .select(memberSub.age.max())
                       .from(memberSub), "age")
                )
        ).from(member)
        .fetch();
  • 프로퍼티나, 필드 접근 생성 방식에서 이름이 다를 때 해결방안이다.
  • 각 별칭을 적용해서 맞춰주는 노가다..

 

📌 DTO 조회 제일 쉬운 방법 - @QueryProjection

@Data
 public class MemberDto {
 	private String username;
 	private int age;
    
 	public MemberDto() {
    	}
        
	@QueryProjection
 	public MemberDto(String username, int age) {
 		this.username = username;
 		this.age = age;
        }
 }
  • 이것을 compileQuerydsl을 이용하 Q파일로 만들어 사용하는 방법이다.
 List<MemberDto> result = queryFactory
        .select(new QMemberDto(member.username, member.age))
        .from(member)
        .fetch();

이 방법은 컴파일러로 타입 체크를 할 수 있으므로 가장 안전한 방법이다.

다만 DTO에 QueryDSL 어노테이션을 유지하는 점과 DTO까지 Q 파일을 생성해야하는 단점이 있다.

또한 DTO의 의존 관계도 문제가 된다..

 

📝 동적 쿼리

"동적 쿼리를 해결하는 두가지 방식"

  • BooleanBuilder
  • Where 다중 파라미터 사용

 

이름이 member1이고 나이가 10인 사람을 찾는다고 해보자...

 

📌 BooleanBuilder

    @Test
    public void 동적쿼리_BooleanBuilder() throws Exception {
        String usernameParam = "member1";
        Integer ageParam = 10;
        List<Member> result = searchMember1(usernameParam, ageParam);
        Assertions.assertThat(result.size()).isEqualTo(1);
    }
    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)
            .fetch();
    }

해당 코드는 들어가는 요소가 null이 아니면 검색 조건에 추가하는 코드이다. 

BooleanBuilder는 이러한 것을 실현시켜주는 where 절을 만들 수 있다.

 

📌 Where 다중 파라미터 사용

    @Test
    public void 동적쿼리_WhereParam() throws Exception {
        String usernameParam = "member1";
        Integer ageParam = 10;
        List<Member> result = searchMember2(usernameParam, ageParam);
        Assertions.assertThat(result.size()).isEqualTo(1);
    }
    private List<Member> searchMember2(String usernameCond, Integer ageCond) {
        return queryFactory
            .selectFrom(member)
            .where(usernameEq(usernameCond), ageEq(ageCond))
            .fetch();
    }
    private BooleanExpression usernameEq(String usernameCond) {
        return usernameCond != null ? member.username.eq(usernameCond) : null;
    }
    private BooleanExpression ageEq(Integer ageCond) {
        return ageCond != null ? member.age.eq(ageCond) : null;
    }

where 다중절은 where에 직접 함수를 때려박을 수 있다.!!!

  • where 조건에 null 값은 무시되는 성질
  • 메서드를 다른 쿼리에도 재활용 가능
  • 쿼리 자체 가독성이 높음

 

 

📝 벌크 연산

쿼리를 한번에 대량으로 데이터 수정이 필요할 때 벌크 연산을 해야한다.
그러나 성능적으로 벌크 연산은 매우 예민한 부분이기에 적절한 처리가 필요하다.

 

쿼리 한번으로 대량 데이터 수정
 long count = queryFactory
        .update(member)
        .set(member.username, "비회원")
        .where(member.age.lt(28))
        .execute();

 

기존 숫자에 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();

 

주의! JPQL 배치와 마찬가지로 ,영속성 컨텍스트를 무시하기 때문에 영속성 컨텍스트 초기화가 필요하다.

 

📝SQL function 호출

SQL function은 JPA와 같이 Dialect에 등록된 내용만 호출할 수 있다

 

member -> M으로 변경하는 replace 함수 사용
 String result = queryFactory
        .select(Expressions.stringTemplate("function('replace', {0}, {1}, {2})", 
	member.username, "member", "M"))
        .from(member)
        .fetchFirst();

 

소문자 변경 후 비교
 .select(member.username)
 .from(member)
 .where(member.username.eq(Expressions.stringTemplate("function('lower', {0})", 
member.username)))
.where(member.username.eq(member.username.lower()))

'Spring > QueryDSL' 카테고리의 다른 글

[Spring]QueryDSL 기본문법  (0) 2024.07.28
[Spring]QueryDSL에 대해 알아보자!  (0) 2024.07.28