-
QueryDSL 중급 문법이커머스 devops/국비교육 2025. 11. 19. 13:11
프로젝션과 결과 반환 - 기본
- 프로젝션: select 대상 지정
// 프로젝션 대상이 하나 List<String> result = queryFactory .select(member.username) .from(member) .fetch();- 프로젝션 대상이 하나면 타입을 명확하게 지정할 수 있다
- 프로젝션 대상이 둘 이상이면 튜플이나 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 조회 @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();- 튜플
- 튜플은 Querydsl 종속적이기 때문에 밖으로 나갈 때는 DTO 변환해서 전달 권장
- 순수 JPA에서 DTO 조회 코드
- 순수 JPA에서 DTO를 조회할 때는 new 명령어를 사용해야 한다
- DTO의 pacakge 이름을 다 적어줘야 해서 코드가 지저분하다
- 생성자 방식만 지원한다
Querydsl 빈 생성 (Bean population)
결과 DTO 반환 3가지 방법
- 프로터피 접근
- 필드 직접 접근
- 생성자 사용
1. 프로퍼티 접근 - Setter
@Test public void findDtoBySetter() { List<MemberDto> result = queryFactory .select(Projections.bean(MemberDto.class, member.username, member.age)) .from(member) .fetch(); for (MemberDto dto : result) { System.out.println("dto = " + dto); } }2. 필드 직접 접근
@Test public void findDtoByField() { List<MemberDto> result = queryFactory .select(Projections.fields(MemberDto.class, member.username, member.age)) .from(member) .fetch(); for (MemberDto dto : result) { System.out.println("dto = " + dto); } }cf. 필드 직접 접근 - 별칭이 다를 때
@Data public class UserDto { private String name; private int age; } @Test public void findDUserDtoByField() { QMember memberSub = new QMember("memberSub"); List<UserDto> result = queryFactory .select(Projections.fields(UserDto.class, member.username.as("name"), ExpressionUtils.as( JPAExpressions .select(memberSub.age.max()) .from(memberSub), "age") )) .from(member) .fetch(); for (UserDto dto : result) { System.out.println("dto = " + dto); } }- 프로퍼티나 필드 접근 생성 방식이 이름이랑 다를 때
- ExpressionUtils.as(source, alias) : 필드나 서브쿼리에 별칭 적용
- username.as("memberName") : 필드에 별칭 적용
3. 생성자 사용
@Test public void findDtoByConstructor() { List<MemberDto> result = queryFactory .select(Projections.constructor(MemberDto.class, member.username, member.age)) .from(member) .fetch(); for (MemberDto dto : result) { System.out.println("dto = " + dto); } }프로젝션과 결과 반환 - @QueryProjection
생성자 + @QueryProjection

QMemberDto 생성 확인 @Test public void findDtoByQueryProjection() { List<MemberDto> result = queryFactory .select(new QMemberDto(member.username, member.age)) .from(member) .fetch(); for (MemberDto dto : result) { System.out.println("dto = " + dto); } }- 컴파일러로 타입을 체크할 수 있으므로 가장 안전한 방법
- DTO에 Querydsl 어노테이션을 유지해야 하며 Q파일을 생성해야 하는 단점이 있다
cf) distinct
List<String> result = queryFactory .select(member.username).distinct() .from(member) .fetch();동적 쿼리를 해결하는 두 가지 방식
- BooleanBuilder
- where 다중 파라미터 사용
동적 쿼리 - BooleanBuilder 사용
@Test public void dynamicQuery_BooleanBuilder() { String usernameParam = "memberA"; Integer ageParam = 10; List<Member> result = searchMember1(usernameParam, ageParam); 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(); }
동적 쿼리 - Where 다중 파라미터 사용
@Test public void dynamicQuery_WhereParam() { String usernameParam = "memberA"; Integer ageParam = 10; List<Member> result = searchMember2(usernameParam, ageParam); assertThat(result.size()).isEqualTo(1); } private List<Member> searchMember2(String usernameCond, Integer ageCond) { return queryFactory .selectFrom(member) .where(usernameEq(usernameCond), ageEq(ageCond)) .fetch(); } private Predicate usernameEq(String usernameCond) { if (usernameCond == null) { return null; } return member.username.eq(usernameCond); } private Predicate ageEq(Integer ageCond) { return ageCond == null ? null : member.age.eq(ageCond); }- where 조건에 null은 무시된다
- 메서드를 다른 쿼리에서도 재활용할 수 있다
- 쿼리 자체 가독성이 높아진다
private List<Member> searchMember2(String usernameCond, Integer ageCond) { return queryFactory .selectFrom(member) .where(allEq(usernameCond, ageCond)) .fetch(); } private BooleanExpression usernameEq(String usernameCond) { return usernameCond == null ? null : member.username.eq(usernameCond); } private BooleanExpression ageEq(Integer ageCond) { return ageCond == null ? null : member.age.eq(ageCond); } private BooleanExpression allEq(String usernameCond, Integer ageCond) { return usernameEq(usernameCond).and(ageEq(ageCond)); }- 조합이 가능하다 (큰 장점)
수정, 삭제 벌크 연산
- 영속성 컨텍스트에 있는 엔티티를 무시하고 실행되기 때문에 배치 쿼리를 실행하고 나면 영속성 컨텍스트를 초기화하는 것이 안전하다
- 영속성 컨텍스트가 DB에서 가져온 값 보다 우선권을 갖는다
- 쿼리 한 번으로 대량 데이터 수정
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();- 기존 숫자에 1 더하기
long count = queryFactory .update(member) .set(member.age, member.age.add(1)) .execute();- 곱하기 : multiply(x)
SQL function 호출하기
- SQL function은 JPA와 같이 Dialect에 등록된 내용만 호출할 수 있다
// member -> M 으로 변경하는 replace 함수 @Test public void sqlFunction() { List<String> result = queryFactory .select(Expressions.stringTemplate("function('replace', {0}, {1}, {2})", member.username, "member", "M")) .from(member) .fetch(); for (String s : result) { System.out.println("s : " + s); } } // 소문자로 변경해서 비교 @Test public void sqlFunction2() { List<String> result = queryFactory .select(member.username) .from(member) // .where(member.username.eq( // Expressions.stringTemplate("function('lower', {0})", member.username))) .where(member.username.eq(member.username.lower())) .fetch(); for (String s : result) { System.out.println("s : " + s); } }

- lower 같은 ansi 표준 함수들은 querydsl이 상당 부분 내장하고 있다
728x90'이커머스 devops > 국비교육' 카테고리의 다른 글
실무 활용 - 스프링 데이터 JPA와 Querydsl (0) 2025.11.19 동적 쿼리와 성능 최적화 조회 (0) 2025.11.19 QueryDSL 기본 문법 (0) 2025.11.19 영속성 컨텍스트와 트랜잭션 & QueryDSL (0) 2025.11.16 RESTful API 설계하기 (0) 2025.11.16