중급 문법
프로젝션과 결과 반환 - 기본
- 프로젝션 : SELECT 대상 지정
- 프로젝션 대상이 하나일 땐 타입을 명확하게 지정할 수 있다.
- 프로젝션 대상이 둘 이상이면 튜플이나 DTO로 조회
List<String> result = queryFactory
.select(member.username)
.from(member)
.fetch();
- 프로젝션 대상이 하나일 때
List<Tuple> result = queryFactory
.select(member.username, member.age)
.from(member)
.fetch();
- 프로젝션 대상이 둘 이상일 때
- Tuple 타입은 Repository 계층을 제외한 Service나 Controller 계층에서 사용하는 것은 좋은 설계가 아니다.
- Tuple 타입은 querydsl 패키지에 속해있다.
- Tuple을 반환할 때는 DTO로 변환해서 전달하는 것이 좋다.
프로젝션과 결과 반환 - DTO 조회
List<MemberDto> result = em.createQuery("select new study.querydsl.dto.MemberDto(m.username,m.age) " +
"from Member m", MemberDto.class).getResultList();
- 순수 JPA에서 DTO 조회시 new 명령어를 사용해야 한다.
- SELECT 절에서 new 명령어와 대상 DTO 패키지 경로 전체를 적어야 한다.
- 생성자 방식만 지원한다.
- Querydsl 빈 생성을 통한 DTO 조회
- 프로퍼티 접근 방식
- 필드 직접 접근 방식
- 생성자 방식
List<MemberDto> result = queryFactory
.select(Projections.bean(MemberDto.class,
member.username,
member.age))
.from(member)
.fetch();
- 프로퍼티 접근 방식으로 Projections.bean()을 통해 타입과 조회할 필드를 명시한다.
- Getter를 통해 값을 가져오고 Setter를 통해 넣는다.
List<MemberDto> result = queryFactory
.select(Projections.fields(MemberDto.class,
member.username,
member.age))
.from(member)
.fetch();
//필드명과 인자가 다른 경우
List<UserDto> result = queryFactory
.select(Projections.fields(UserDto.class,
member.username.as("name"),
member.age))
.from(member)
.fetch();
- 필드 접근 방식으로 Projections.field()를 사용한다.
- 별도의 Getter / Setter가 없더라도 바로 필드에 값을 넣는다.
- DTO 필드명과 인자가 다를 때는 as()를 사용해서 필드명과 동일하게 지정해준다.
List<MemberDto> result = queryFactory
.select(Projections.constructor(MemberDto.class,
member.username,
member.age))
.from(member)
.fetch();
- 생성자 방식으로 Projections.constructor()를 사용한다.
- DTO 생성자의 인자 타입과 constructor() 인자 타입이 일치해야한다.
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();
- ExpressionUtils.as(source, alias) : 필드나 서브 쿼리에 별칭 적용
프로젝션 결과 반환 - @QueryProjection
- DTO 생성자에 @QueryProjection으로 선언하면 DTO도 Q클래스가 생성된다.
public class MemberDto {
private String username;
private int age;
@QueryProjection
public MemberDto(String username, int age) {
this.username = username;
this.age = age;
}
}
List<MemberDto> result = queryFactory
.select(new QMemberDto(member.username, member.age))
.from(member)
.fetch();
- @QueryProjection 특징
- 장점
- 컴파일 시점에 오류, 타입 체크를 할 수 있다.
- 타입을 확인할 수 있기에 명확한 타입을 작성할 수 있다.
- 단점
- DTO 클래스가 Querydsl에 의존하게 된다.
- Q클래스를 생성해줘야 한다.
- 장점
동적 쿼리 - BooleanBuilder 사용
- 동적 쿼리를 해결하는 방식
- BooleanBuilder
- where 다중 파라미터 사용
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를 사용해서 이름과 나이에 따른 동적 쿼리
동적 쿼리 - where 다중 파라미터 사용
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이 있으면 무시하기 때문에 동적 쿼리를 작성할 수 있다.
- 별도의 메소드로 작성함에 따라 재사용할 수 있다.
- 쿼리 자체의 가독성이 높아진다.
private BooleanExpression allEq(String usernameCond, Integer ageCond) {
return usernameEq(usernameCond).and(ageEq(ageCond));
}
private List<Member> searchMember2(String usernameCond, Integer ageCond) {
return queryFactory
.selectFrom(member)
.where(allEq(usernameCond, ageCond)
.fetch();
}
- BooleanExpression 타입으로 하면 통해 여러 조건을 하나로 조립할 수 있다.
- 조립하는 경우 null 체크는 주의해서 해야한다.
수정, 삭제 배치 쿼리(벌크 연산)
@Test
public void bulkUpdate() throws Exception {
long count = queryFactory
.update(member)
.set(member.username, "비회원")
.where(member.age.loe(20))
.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() throws Exception {
long count = queryFactory
.update(member)
.set(member.age, member.age.add(1))
// .set(member.age, member.age.divide(2))
// .set(member.age, member.age.multiply(2))
// .set(member.age, member.age.add(-1))
.execute();
}
@Test
public void bulkDelete() throws Exception {
long count = queryFactory
.delete(member)
.where(member.age.gt(18))
.execute();
}
- 벌크 연산은 영속성 컨텍스트를 무시하고 바로 DB로 쿼리를 날린다.
- 영속성 컨텍스트와 DB 데이터가 일치하지 않는 경우에는 영속성 컨텍스트의 데이터가 우선 시 되기에 벌크 연산 이후 영속성 컨텍스트를 비우기 위해서 flush(), clear()가 필요하다.
- 뺄셈 연산은 제공되지 않기에 add()에 -1과 같이 작성한다.
SQL function 호출
- SQL function은 JPA와 같이 Dialect에 등록된 내용만 호출할 수 있다.
/**
* 회원명을 소문자로 변경
*/
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();
출처 : [인프런 김영한 실전 Querydsl]
https://www.inflearn.com/course/querydsl-%EC%8B%A4%EC%A0%84/dashboard
실전! Querydsl 강의 | 김영한 - 인프런
김영한 | Querydsl의 기초부터 실무 활용까지, 한번에 해결해보세요!, 복잡한 쿼리, 동적 쿼리는 이제 안녕! Querydsl로 자바 백엔드 기술을 단단하게. 🚩 본 강의는 로드맵 과정입니다. 본 강의는 자
www.inflearn.com
'Spring > [인프런 김영한 실전 Querydsl]' 카테고리의 다른 글
[인프런 김영한 실전 Querydsl] 실무 활용 - 스프링 데이터 JPA와 Querydsl (2) | 2024.09.26 |
---|---|
[인프런 김영한 실전 Querydsl] 실무 활용 - 순수 JPA와 Querydsl (2) | 2024.09.24 |
[인프런 김영한 실전 Querydsl] 기본 문법 (0) | 2024.09.23 |
[인프런 김영한 실전 Querydsl] 프로젝트 환경설정 (2) | 2024.09.23 |