빙응의 공부 블로그
[Spring]QueryDSL 중급 문법 본문
📝프로젝션
프로젝션이란 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 |