프로젝션
- 프로젝션
- select 대상 지정
프로젝션 대상이 하나인 경우
1
2
3
4
List<String> result = queryFactory
.select(memeber.username)
.from(member)
.fetch();
- 프로젝션 대상이 하나라면 타입을 명확하게 지정할 수 있다.
- 프로젝션 대상이 둘 이상이면 튜플이나 DTO로 조회한다.
튜플 조회
프로젝션 대상이 둘 이상일 때 사용된다.
1
2
3
4
5
6
7
8
9
10
List<Tuple> result = queryFactory
.select(memeber.username, memeber.age)
.from(memeber)
.fetch();
for (Tuple tuple : result) {
String username = tuple.get(memeber.username);
Integer age = tuple.get(member.age);
System.out.println("username=" + username);
}
- Tuple은 Querydsl에 종속적이기 때문에 기술 변경을 생각하면 외부에서 사용되는 것이 바람직하지 않다.
- 사용하지 않거나, 리포지토리 내에서만 사용하는 등의 방식을 선택하자.
DTO로 조회
- MemberDto
1
2
3
4
5
6
7
8
9
10
11
12
13
@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로 조회
1
2
3
4
List<MemberDto> result = em.createQuery(
"select new study.querydsl.dto.MemberDto(m.username, m.age)" +
" from Member m", MemberDto.class)
.getResultList();
- 순수 JPA에서 DTO를 조회할 때 new 명령어를 사용한다.
- 패키지를 모두 적어주어야 해 지저분하다.
- 생성자 방식만 지원한다.
Querydsl에서의 DTO 조회
결과를 DTO로 반환할 때 3가지 방법을 지원한다.
- 프로퍼티 접근
- 필드 직접 접근
- 생성자 사용
프로퍼티 접근 - setter
1
2
3
4
5
6
List<MemberDto> result = queryFactory
.select(Projections.bean(MemberDto.class,
memeber.username,
memeber.age))
.from(member)
.fetch();
- getter, setter가 필요하다.
필드 직접 접근
1
2
3
4
5
6
List<MemberDto> result = queryFactory
.select(Projections.fields(MemberDto.class,
memeber.username,
memeber.age))
.from(member)
.fetch();
별칭이 다른 경우
1
2
3
4
5
@Data
public class UserDto {
private String name;
private int age;
}
1
2
3
4
5
6
7
8
9
10
List<UserDto> fetch = queryFactory
.select(Projections.fields(UserDto.class,
memeber.username.as("name"),
ExpressionUtils.as(
JPAExpressions
.select(memeberSub.age.max())
.from(memeberSub), "age")
)
).from(memeber)
.fetch();
- 프로퍼티나 필드 접근 생성 방식에서 이름이 다를 때 위와 같이 해결할 수 있다.
- ExpressionUtils.as(source, alias): 필드나 서브 쿼리에 별칭 적용
생성자 사용
1
2
3
4
5
6
List<MemberDto> result = queryFactory
.select(Projections.constructor(MemberDto.class,
member.username
member.age))
.from(member)
.fetch();
@QueryProjection
위의 3가지 방법과 다른 방식으로 DTO로 반환하는 방법 중 하나이다.
1
2
3
4
5
6
7
//MemberDto
@QueryProjection
public MemberDto(String username, int age) {
this.username = username;
this.age = age;
}
- Dto 클래스의 생성자에 @QueryProjection 애노테이션을 붙인다.
- gradle > complieJava
- QMemberDto의 생성을 확인한다.
활용
1
2
3
4
List<MemberDto> result = queryFactory
.select(new QMemberDto(memeber.username, member.age))
.from(member)
.fetch();
이 방법은 컴파일러로 타입을 체크할 수 있어 가장 안전한 방법이다.
하지만 DTO에 QueryDSL 어노테이션을 유지해야 한다는 점과 DTO까지 Q파일을 생성해야 한다는 단점이 있다.
distinct
1
2
3
4
List<String> result = queryFactory
.select(member.username).distinct()
.from(member)
.fetch();
동적 쿼리
동적 쿼리를 처리하는 방식에는 2가지가 있다.
- BooleanBuilder
- Where 다중 파라미터 사용
BooleanBuilder
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Test
public void 동적쿼리_BooleanBuilder() {
String usernameParam = "memeber1";
Integer ageParam = 10;
List<Member> result = searchMember1(usernameParam, ageParam);
}
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 다중 파라미터 사용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Test
public void 동적쿼리_WhereParam() {
String usernameParam = "memeber1";
Integer ageParam = 10;
List<Member> result = searchMember2(usernameParam, ageParam);
}
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 값이 무시되기 때문에 이런 방식이 가능하다.
- 메서드를 다른 쿼리에서도 재사용 할 수 있다는 장점.
- 쿼리 자체의 가독성도 높아진다.
1
return usernameEq(usernameCond).and(ageEq(ageCond));
위와 같이 조합도 가능하다.
수정, 삭제 벌크연산
쿼리 한번으로 대량 데이터 수정
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
long count = queryFactory
.update(member)
.set(member.username, "비회원")
.where(member.age.lt(28))
.execute();
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 함수 사용
1
2
3
4
5
String result = queryFactory
.select(Expressions.stringTemplate("function('replace', {0}, {1}, {2})",
memeber.username, "member", "M"))
.from(member)
.fetchFirst();
“member”를 “M” 으로 변경하는 것이다.