프로젝션이란?
select 대상을 지정하는 것
프로젝션 대상이 하나
@Test
public void simpleProjection() {
List<String> result = queryFactory.select(member.username)
.from(member)
.fetch();
for (String s : result) {
System.out.println("s = " + s);
}
}
실행 결과
프로젝션 대상이 둘 이상일 때 사용합니다
com.querydsl.core.Tuple
@Test
public void tupleProjection() {
List 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.username);
System.out.println("username = " + username);
System.out.println("age = " + age);
}
}
- Tuple은 리포지토리 계층 안에서 쓰는 정도는 괜찮지만, 그 밖의 계층에서는 DTO로 변환하는 것이 좋습니다
실행 결과
![](https://velog.velcdn.com/images/urtimeislimited/post/aec15708-4ea2-448f-ae6d-6fa4b091e789/image.png)
## 프로젝션과 결과 반환 - DTO 조회
### 순수 JPA에서 DTO 조회
MemberDto
```java
package study.querydsl.dto;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class MemberDto {
private String username;
private int age;
public MemberDto(String username, int age) {
this.username = username;
this.age = age;
}
}
순수 JPA에서 DTO 조회 코드
@Test
public void findDtoByJPQL() {
List<MemberDto> result = em.createQuery("select new study.querydsl.dto.MemberDto(m.username, m.age) from Member m", MemberDto.class)
.getResultList();
for (MemberDto memberDto : result) {
System.out.println("memberDto = " + memberDto);
}
}
실행 결과
결과를 DTO 반환할 때 사용하며, 3가지 방법을 지원합니다
@Test
public void findDtoBySetter() {
List<MemberDto> result = queryFactory
.select(Projections.bean(MemberDto.class,
member.username,
member.age))
.from(member)
.fetch();
for (MemberDto memberDto : result) {
System.out.println("memberDto = " + memberDto);
}
실행 결과
@Test
public void findDtoByField() {
List<MemberDto> result = queryFactory
.select(Projections.fields(MemberDto.class,
member.username,
member.age))
.from(member)
.fetch();
for (MemberDto memberDto : result) {
System.out.println("memberDto = " + memberDto);
}
}
실행 결과
별칭이 다를 때
UserDto
package study.querydsl.dto;
import lombok.Data;
@Data
public class UserDto {
private String name;
private int age;
public UserDto() {
}
public UserDto(String name, int age) {
this.name = name;
this.age = age;
}
}
@Test
public void findUserDto() {
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 userDto : result) {
System.out.println("userDto = " + userDto);
}
}
실행 결과
@Test
public void findDtoByConstructor() {
List<MemberDto> result = queryFactory
.select(Projections.constructor(MemberDto.class,
member.username,
member.age))
.from(member)
.fetch();
for (MemberDto memberDto : result) {
System.out.println("memberDto = " + memberDto);
}
}
실행 결과
생성자 + @QueryProjection
package study.querydsl.dto;
import com.querydsl.core.annotations.QueryProjection;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class MemberDto {
private String username;
private int age;
@QueryProjection
public MemberDto(String username, int age) {
this.username = username;
this.age = age;
}
}
@QueryProject 추가 후, compileQuerydsl 클릭
QMemberDto 생성되었습니다
QueryProjection 활용
@Test
public void findDtoByQueryProjection() {
List<MemberDto> result = queryFactory
.select(new QMemberDto(member.username, member.age))
.from(member)
.fetch();
for (MemberDto memberDto : result) {
System.out.println("memberDto = " + memberDto);
}
}
실행 결과
distinct 사용
List<String> result = queryFactory
.select(member.username).distinct()
.from(member)
.fetch();
동적 쿼리를 해결하는 두 가지 방식
BooleanBuilder 사용
@Test
public void dynamicQuery_BooleanBuilder() throws Exception {
String usernameParam = "member1";
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();
}
BooleanBuilder를 통한 동적 쿼리 생성은 빌더를 생성 후 필요한 조건을 null 확인 여부에 따라 and 또는 or 등으로 더해주면 됩니다
실행 결과
@Test
public void dynamicQuery_WhereParam() throws Exception {
String usernameParam = "member1";
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))
// .where(allEq(usernameCond, 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;
}
}
실행 결과
메서드 조합 가능
private BooleanExpression allEq(String usernameCond, Integer ageCond) {
return usernameEq(usernameCond).and(ageEq(ageCond));
}
@Test
@Commit
public void bulkUpdate() {
// member1 = 10 -> DB member1
// member2 = 20 -> DB member2
// member3 = 30 -> DB member3
long count = queryFactory
.update(member)
.set(member.username, "비회원")
.where(member.age.lt(28))
.execute(); // count: 용량을 받은 회원 수
// member1 = 10 -> DB 비회원
// member2 = 20 -> DB 비회원
// member3 = 30 -> DB member3
// member4 = 40 -> DB member4
List<Member> result = queryFactory
.selectFrom(member)
.fetch();
for (Member member1: result) {
System.out.println("member1 = " + member1);
}
}
실행 결과
db
소스 실행
기존 숫자에 1 더하기
@Test
public void bulkAdd() {
long count = queryFactory
.update(member)
.set(member.age, member.age(add(1))
.execute();
}
multiply(x)
실행 결과
쿼리 한번으로 대량 데이터 삭제
@Test
public void buildDelete() {
long count = queryFactory
.delete(member)
.where(member.age(gt(18))
.execute();
}
실행 결과
주의
JPQL 배치와 마찬가지로, 영속성 컨텍스트에 있는 엔티티를 무시하고 실행되기 때문에 배치 쿼리를 실행하고 나면 영속성 컨텍스트를 초기화 하는 것이 안전합니다
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);
}
}
실행 결과
lower 함수 - 소문자로 변경해서 비교
@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이 대부분 내장하고 있기 때문에 다음과 같이 처리해도 결과는 같습니다
.where(member.username.eq(member.username.lower()))
실행 결과