프로젝션과 결과 반환 - 기본
- 프로젝션: SELECT 대상을 지정한다.
- 프로젝션 대상이 하나인 경우, 타입을 명확하게 지정 가능하다.
- 둘 이상인 경우는 튜플이나 DTO로 조회한다.
Ex) 프로젝션 결과 반환 - 기본
- List<String> result = queryFactory.select(member.username). ....
- 위와 같이 result의 타입을 username 형태인 String으로 정해줄 수 있다.
Ex) 프로젝션 결과 반환 - 튜플 조회
- List<Tuple> result = queryFactory.select(member.username, member.age). ....
프로젝션과 결과 반환 - DTO
Ex) 순수 JPA에서 DTO 조회
<hide/>
List<MemberDto> result = em.createQuery(
"SELECT new study.querydsl.dto.MemberDto(m.username, m.age) FROM MEMBER m",
MemberDto.class).getResultList();
- 순수 JPA에서 DTO를 조회할 때는 new 명령어를 사용해야한다.
- DTO의 패키지를 모두 적어야해서 코드가 복잡하다.
- 생성자 방식만 지원한다.
Querydsl 빈 생성(bean population)
- 결과를 DTO로 반환할 때 사용한다.
- 1) 프로퍼티에 접근한다.
- 2) 필드에 직접 접근한다.
- 3) 생성자를 사용한다.
Ex) 프로퍼티 접근 - setter
<hide/>
List<MemberDto> result = queryFactory.select(
Projections.bean(MemberDto.class, member.username, member.age))
.from(member)
.fetch();
- Projections.bean(): 프로젝션의 대상이되는 엔티티의 필드들을 하나의 클래스로 매핑해서 결괏값으로 반환하는 방식
Ex) 필드에 직접 접근
<hide/>
List<MemberDto> result = queryFactory.select(Projections
.fields(MemberDto.class, member.username, member.age))
.from(member)
.fetch();
- Projections.fields(): 프로젝션 대상이 되는 엔티티의 필드 중에서 특정 필드들만 선택해서 결괏값으로 반환하는 방식이다.
Ex) 별칭이 다를 때
<hide/>
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();
- 프로퍼티나 필드 접근 생성 방식에서 이름이 다를 때 사용한다.
- ExpressionUtils.as(source, alias): 필드나 서브쿼리에 별칭을 사용한다.
- username.as("memberName"): 필드에 별칭을 사용한다.
Ex) 생성자 사용
<hide/>
List<MemberDto> result = queryFactory
.select(Projections.constructor(MemberDto.class,
member.username,
member.age))
.from(member)
.fetch();
- Projections.constructor()
프로젝션과 결과 반환 - @QueryProjection
Ex) 생성자에 @QueryProjection 적용하기
<hide/>
@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;
}
}
- DTO클래스를 위와 같이 만들고 @QueryProjection 애너테이션을 붙인다.
- 터미널에 다음과 같이 입력해서 컴파일해주면 쿼리 관련된 파일이 새롭게 생성된다. 또는 gradle 탭에서 clean한 다음에 compile 클릭.
- ./gradlew compileQuerydsl
- 그러고나서 build 폴더를 확인해보면 다음과 같이 dto에 대한 클래스가 만들어진다.
<hide/>
List<MemberDto> result = queryFactory
.select(new QMemberDto(
member.username,
member.age))
.from(member)
.fetch();
- 이 방식은 컴파일러로 타입을 체크할 수 있으므로 안전한 방법이다.
- 그러나 DTO에@QueryProjection을 유지해야 하는 점과 DTO까지 Q 파일을 생성해야하는 단점이 있다.
<hide/>
List<String> result0 = queryFactory
.select(member.username).distinct()
.from(member)
.fetch();
- distinct는 JPQL의 distinct와 같다.
동적 쿼리 해결 방법
- 동적 쿼리: 프로그램 실행 중에 조건에 따라 쿼리문이 동적으로 생성되는 쿼리를 말한다. 실행 시점에 쿼리문이 생성되며 쿼리문에 사용되는 조건문, 테이블명, 칼럼명 등이 로직에 따라 달라질 수 있는 쿼리를 말한다.
- 예를 들어, 검색 조건이 동적으로 결정되는 검색 기능을 구현할 때, 사용자가 입력한 검색어에 따라 쿼리문이 동적으로 생성되어야한다. 이 때 동적 쿼리를 사용하면 적절한 쿼리문을 생성해서 검색 기능을 구현할 수 있다.
Ex 1) BooleanBuilder
<hide/>
// 동적 쿼리 - BooleanBuilder
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();
}
@Test
public void dynamicQuery_booleanBuilder() throws Exception {
String usernameParam = "member1";
Integer ageParam = 10;
List<Member> result = searchMember1(usernameParam, ageParam);
Assertions.assertThat(result.size()).isEqualTo(1);
}
Ex 2) WHERE 다중 파라미터 사용
<hide/>
// 동적 쿼리 - where 다중 파라미터
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 List<Member> searchMember2(String usernameCond, Integer ageCond) {
return queryFactory.selectFrom(member)
.where(usernameEq(usernameCond), ageEq(ageCond))
.fetch();
}
@Test
public void dynamicQuery_whereParam() throws Exception {
String usernameParam = "member1";
Integer ageParam = 10;
List<Member> result = searchMember2(usernameParam, ageParam);
Assertions.assertThat(result.size()).isEqualTo(1);
}
- where 절 안에 매개변수로 여러 개 넣어줄 수 있도록 여러 개의 "eq" 메서드를 만든다.
- search() 메서드는 실질적인 쿼리 역할을 한다.
- where 절 안에 null 값이 있으면 무시된다.
- 메서드를 다른 쿼리에서도 재활용할 수 있기 때문에 효율적이다.
- 가독성이 올라간다.
- cf) and()를 이용해서 ageEq().and.usernameEq() 와 같은 형태로 조합도 가능하다.
수정, 삭제 벌크 연산
Ex) 문자열, 숫자, 대량 삭제
<hide/>
long count = queryFactory.update(member)
.set(member.username, "비회원")
.where(member.age.lt(28))
.execute();
long count1 = queryFactory.update(member)
.set(member.age, member.age.add(1))
.execute();
long count2 = queryFactory.delete(member)
.where(member.age.gt(18))
.execute();
- update().set(원래 필드, 바뀐 값)
- delete() 해주면 조건에 맞는 데이터를 대량으로 삭제할 수 있다.
SQL function 호출하기
- SQL function은 SUM(), MAX(), COUNT()와 같이 데이터베이스 제공하는 기본 함수 또는 사용자 정의 함수를 말하며 데이터를 조작하거나 계산하는데 사용되는 함수를 말한다.
- SQL_function은 JPA와 같이 Dialect에 등록된 내용만 호출 가능하다.
- member => 'M' 으로 변경하는 replace 함수를 사용한다.
Ex) member => M으로 변경하는 replace 함수 사용
<hide/>
String result = queryFactory.select(Expressions.stringTemplate("function('replace', {0}, {1}, {2})", member.username, "member", "M"))
.from(member)
.fetchFirst();
Ex) 소문자로 바꿔서 비교하기
<hide/>
String result = queryFactory.select(member.username)
.from(member)
.where(member.username
.eq(Expressions.stringTemplate("function('lower', {0})", member.username))).fetchFirst();
- lower() 같은 ansi(Amerinal National Standards Institute, 미국 국립 표준 협회) 표준 함수들은 querydsl에서 기본적으로 내장하고 있다.
출처 - https://www.inflearn.com/course/querydsl-%EC%8B%A4%EC%A0%84
'Spring Framework > [인프런] 실전! QueryDSL' 카테고리의 다른 글
Chapter 06. Spring Data JPA가 지원하는 querydsl 기능 (0) | 2023.05.13 |
---|---|
Chapter 05. 실무 활용 - 스프링 데이터 JPA와 Querydsl (2) | 2023.05.08 |
Chapter 04. 실무 활용 - 순수 JPA와 Querydsl (1) | 2023.05.06 |
Chapter 02. 기본 문법 (0) | 2023.04.13 |
Chapter 01. QueryDsl 프로젝트 생성 및 환경 설정 (0) | 2023.04.09 |