* F2: IntelliJ 오류난 곳으로 바로 이동
프로젝션과 결과 반환 - 기본
- 프로젝션: select 대상 지정
- 튜플은 querydsl 종속적인 자료형이기 때문에 repository 계층이 아닌 controller나 service 단에서는 사용 지양
- 다른 계층으로 넘길 때는 DTO 사용 권장
// 프로젝션 대상 하나
@Test
public void simpleProjection() {
List<String> result = queryFactory
.select(member.username) // 이름만 지정
.from(member)
.fetch();
for (String s : result) {
System.out.println("s = " + s);
}
}
// 프로젝션 대상 여러개
@Test
public void tupleProjection() {
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 조회
- JPQL
- new 명령어와 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);
}
}
- 프로퍼티 접근 - Setter
- Projections.bean()
@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);
}
}
- 필드 직접 접근
- Projections.fields()
@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);
}
}
- 필드명 다를 때: .as() 사용
//UserDto
@Data
public class UserDto {
private String name;
private int age;
}
//Test
@Test
public void findUserDtoByField() {
List<UserDto> result = queryFactory
.select(Projections.fields(UserDto.class,
member.username,
member.age))
.from(member)
.fetch();
for (UserDto userDto : result) {
System.out.println("userDto = " + userDto);
}
}
//결과
userDto = UserDto(name=null, age=10)
userDto = UserDto(name=null, age=20)
userDto = UserDto(name=null, age=30)
userDto = UserDto(name=null, age=40)
//해결 방안
@Test
public void findUserDtoByField() {
List<UserDto> result = queryFactory
.select(Projections.fields(UserDto.class,
member.username.as("name"),
member.age))
.from(member)
.fetch();
}
- 필드나 서브쿼리에 별칭 적용: ExpressionsUtils.as(source, alias)
//서브 쿼리에 별칭 적용
@Test
public void findUserDtoByField() {
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);
}
}
//결과
userDto = UserDto(name=1, age=40)
userDto = UserDto(name=2, age=40)
userDto = UserDto(name=3, age=40)
userDto = UserDto(name=4, age=40)
- 생성자 사용
- 생성자의 파라미터 순서와 Projections.constructor()의 인자 순서가 같아야함
- DTO 필드의 데이터 타입을 체크하기 때문에 엔티티와 필드명 달라도 잘 작동함
- UserDto에 생성자를 잘 정의하고, 아래 코드에서 MemberDto를 모두 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
- DTO에 해당하는 Q타입 생성됨
- 장점: 컴파일 시점에 오류 체크 가능 (위의 세가지 방식은 런타임 시점에 발견)
- 단점: DTO도 Q파일을 만들어야 하고, Querydsl 어노테이션을 유지하여 라이브러리에 종속적이게 된다
//MemberDto
@QueryProjection
public MemberDto(String username, int age) {
this.username = username;
this.age = age;
}
//Test
@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() {
String usernameParam = "1";
Integer ageParam = 30;
List<Member> result = searchMember1(usernameParam, ageParam);
assertThat(result.size()).isEqualTo(2);
}
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.or(member.age.eq(ageCond));
}
return queryFactory
.selectFrom(member)
.where(builder)
.fetch();
}
- where() 다중 파라미터 사용
- 인자로 null이 들어가면 무시
- 메서드로 조건문을 따로 빼기 때문에, 가독성이 좋아지고 다른 쿼리에서도 재활용 가능
- 메서드를 조합해 사용 가능 (null 체크 주의)
- 메서드 반환 타입을 Predicate 말고 BooleanExpression으로 해야 함
@Test
public void dynamicQuery_WhereParam() {
String usernameParam = "1";
Integer ageParam = 30;
List<Member> result = searchMember2(usernameParam, ageParam);
assertThat(result.size()).isEqualTo(3);
}
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) {
if (ageCond != null) {
return member.age.eq(ageCond);
} else {
return null;
}
}
private BooleanExpression allEq(String usernameCond, Integer ageCond) {
return usernameEq(usernameCond).and(ageEq(ageCond));
}
수정, 삭제 벌크 연산
- 쿼리 한번에 대량의 데이터를 수정하거나 삭제
- update(), set(), execute()
- delete(), execute()
- 영속성 컨텍스트를 무시하고 DB에 직접적으로 실행되기 때문에 배치 쿼리 이후 영속성 컨텍스트 초기화 권장
// 수정
@Test
public void bulkUpdate() {
//영속성 컨텍스트에는 영향을 주지 않는 벌크 수정 쿼리
long count = queryFactory
.update(member)
.set(member.username, "비회원")
.where(member.age.lt(28))
.execute();
//영속성 컨텍스트 초기화
em.flush();
em.clear();
//영속성 컨텍스트에서 조회하는 쿼리
List<Member> result = queryFactory
.selectFrom(member)
.fetch();
for (Member member1 : result) {
System.out.println("member1 = " + member1);
}
}
// 숫자 더하기
@Test
public void bulkAdd() {
long count = queryFactory
.update(member)
.set(member.age, member.age.add(1))
.execute();
}
// 숫자 곱하기
@Test
public void bulkMultiply() {
long count = queryFactory
.update(member)
.set(member.age, member.age.multiply(2))
.execute();
}
// 삭제
@Test
public void bulkDelete() {
long count = queryFactory
.delete(member)
.where(member.age.gt(18))
.execute();
}
SQL function 호출
- JPQ와 같이 사용하는 DB의 방언에 등록된 내용한 호출 가능
- stringTemplate() 사용
- member -> M으로 변경하는 replace 함수 사용
// 모든 멤버를 대상으로 이름에 member를 M으로 변경하여 반환
@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 함수 사용
- lower 같은 ansi 표준 함수들은 querydsl이 상당수 내장하고 있음
// 이름을 소문자로 변경해도 원래와 이름이 같은 멤버 조회
@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);
}
}
'course > inflearn' 카테고리의 다른 글
[실전! Querydsl] 실무 활용 - 스프링 데이터 JPA와 Querydsl (0) | 2022.11.24 |
---|---|
[실전! Querydsl] 실무 활용 - 순수 JPA와 Querydsl (0) | 2022.11.23 |
[실전! Querydsl] 기본 문법 (1) | 2022.11.22 |
[JPA 프로그래밍 기본편] JPQL - 중급 문법 (0) | 2022.11.10 |
[JPA 프로그래밍 기본편] JPQL - 기본 문법 (0) | 2022.11.03 |