JPA

QueryDSL 중급 문법

taey 2024. 9. 11. 00:09

프로젝션과 결과 반환 - 기본

프로젝션 : select 대상 지정

 

프로젝션 대상이 하나

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

튜플 조회

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

com.querydsl.core.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);
}

 


프로젝션과 결과 반환 - DTO 조회

순수 JPA에서 DTO 조회

  • 순수 JPA에서 DTO를 조회할 때는 new 명령어를 사용해야 함
  • DTO의 package 이름을 다 적어줘야해서 지저분함
  • 생성자 방식만 지원함

 

Querydsl 빈 생성 (Bean population)

결과를 DTO 반환할 때 사용

다음 3가지 방법 지원

  • 프로퍼티 접근
  • 필드 직접 접근
  • 생성자 사용

프로퍼티 접근 - Setter

List<MemberDto> result = queryFactory
		.select(Projections.bean(MemberDto.class,
			member.username,
			member.age))
		.from(member)
		.fetch();
  • 기본 생성자 필요
  • Setter가 필요하다

 

필드 직접 접근

List<MemberDto> result = queryFactory
		.select(Projections.fields(MemberDto.class,
			member.username,
			member.age))
		.from(member)
		.fetch();
  • 기본 생성자가 필요 없이, 바로 필드에 접근

 

별칭이 다를 때

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();
  • 프로퍼티나, 필드 접근 생성 방식에서 이름이 다를 때 해결 방안
  • ExpressionUtils.as(source, alias) : 필드나, 서브 쿼리에 별칭 적용
  • username.as("memberName") : 필드에 별칭 적용

생성자 사용

List<MemberDto> result = queryFactory
		.selct(Projections.constructor(MemberDto.class,
        		member.username,
			member.age))
		.from(member)
		.fetch();
  • DTO 클래스생성자에 @QueryProjection이 필요

 

프로젝션과 결과 반환 - @QueryProjection

생성자 + @QueryProjection

package study.querydsl.dto;
import com.querydsl.core.annotations.QueryProjection;
import lombok.Data;

@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;
     }
}
  • ./gradlew compileQuerydsl
  • QMemberDto 생성 확인

 

@QueryProjection 활용

List<MemberDto> result = queryFactory
		.select(new QMemberDto(member.username, member.age))
        .from(member)
        .fetch();

이 방법은 컴파일러로 타입을 체크할 수 있으므로 가장 안전한 방법이다. 다만 DTO에 QueryDSL 어노테이션을 유지해야 하는 점과 DTO까지 Q 파일을 생성해야 하는 단점이 있다.

 

 

distinct

List<String> result = queryFactory
		.select(member.username).distinct()
        .from(member)
        .fetch();
참고 : distinct는 JPQL의 distinct와 같다.

 


 

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

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

BooleanBuilder 사용

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

 

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 조건에 null 값은 무시된다.
  • 메서드를 다른 쿼리에서도 재활용할 수 있다.
  • 쿼리 자체의 가독성이 높아진다.

 

조합 가능

private BooleanExpression allEq(String usernameCond, Integer ageCond) {
	return usernameEq(usernameCond).and(ageEq(ageCond));
}

 


수정, 삭제 벌크 연산

 

쿼리 한 번으로 대량 데이터 수정

long count = queryFactory
	.update(member)
        .set(member.username, "비회원")
        .where(member.age.lt(28))
        .execute();

 

쿼리 한 번으로 대량 데이터 삭제

long count = queryFactory
	.delete(member)
	.where(member.age.gt(18))
	.execute();

 

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

 


SQL fuctoin 호출하기

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)))

 

lower 같은 ansi 표준 함수들은 querydsl이 상당 부분 내장하고 있다. 따라서 다음과 같이 처리해도 결과는 같다.

.where(member.username.eq(member.username.lower()))

'JPA' 카테고리의 다른 글

Querydsl 동적 쿼리 사용 예시  (0) 2024.09.11
QueryDSL 기본 문법  (0) 2024.09.10
JPQL 중급 문법과 기능  (2) 2024.09.04
JPQL 기본 문법과 기능  (0) 2024.09.03
JPA 쿼리 지원 방법  (1) 2024.09.03