22.07.14
프로젝션과 결과 반환 - DTO 조회
순수 JPA에서 DTO 조회
new 연산자 + 필드 초기화 생성자 + DTO.class로 반환
(패키지 전부 기입)
em.createQuery( "select new study.querydsl.dto.MemberDto(m.username, m.age) "
+ "from Member m", MemberDto.class)
Querydsl 빈 생성(Bean population)
결과를 DTO 반환할 때 사용
다음 3가지 방법 지원
-프로퍼티 접근 (setter)
DTO에는 기본 생성자 필수. 우선 객체 생성 후, setter 호출하는 방식
List<MemberDto> result = queryFactory
.select(Projections.bean(MemberDto.class, member.username, member.age))
.from(member)
.fetch();
-필드 직접 접근
private이어도 가능
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();
* 엔티티 필드명과 DTO 필드명이 다를 때 *
프로퍼티, 필드 접근 방식에서는 생성자 방식과 다르게 필드명을 정확히 알아야 함. 기본은 엔티티의 필드명
데이터베이스 쿼리 시에 엔티티와 DTO의 필드명이 다르면 매칭되지 않고 null로 초기화가 됨
=> 별칭 사용
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();
QEntity.field.as("DTOfieldName")) : 필드에 별칭 적용
ExpressionUtils.as(source, alias) : 필드나, 서브 쿼리에 별칭 적용
프로젝션과 결과 반환 - @QueryProjection
Quertdsl에 의존하는 생성자 방식의 일환
1. DTO 생성자에 @QueryProjection (com.querydsl.core.annotation.QueryProjection)
2. ./gradlew compileQuerydsl 호출
3. 해당 DTO 기반 Q파일 생성
4. List<Dto> result = qf.select(new Qdto(필드1, 필드2, ...))... //파라미터의 타입
생성자 방식과 차이점
생성자 방식은 생성자와 맞지 않는 파라미터를 넣어도 컴파일 에러가 아닌 런타임 에러가 발생한다.
@QueryProjection 방식은 실제 객체를 생성할 때 처럼 컴파일 단계에서 타입을 체크할 수 있어 안전하다.
그러나 DTO에 QueryDSL 어노테이션을 유지해야 하는 점과 DTO까지 Q 파일을 생성해야 하는 단점이 있다.
동적 쿼리
*동적 쿼리를 해결하는 두가지 방식*
- Boolean Builder
- Where 다중 파라미터 사용
BooleanBuilder 사용
조건 파라미터가 null인지 아닌지에 따라 검색 조건이 동적으로 바뀜
BooleanBuilder builder = new BooleanBuilder(초기 조건);
if (조건 파라미터1 != null)
builder.and(조건1)
if(조건 파라미터2 != null)
builder.and(조건2)
...
.where(builder)
Where 다중 파라미터 사용
.where(Predicate1, Predicate2,...)
where 의 다중 파라미터는 and 로 묶인다. null 조건은 무시
Predicate를 생성하는 메서드
return (조건 파라미터 != null) ? 조건 : null;
=> 이런 메서드들을 and로 조립해서 여러 조건을 포함하는 하나의 메서드로 만들 수 있다. (재사용성, 가독성 ↑)
이때, null 체크 주의!
수정, 삭제 벌크 연산
벌크 연산: 쿼리 한 번으로 대량 데이터 수정. 반환 값은 수정된 데이터 수
JPA의 영속성 컨텍스트와는 무관 -> 변경 감지X, DB에 직접 쿼리
수정 벌크 연산
ex. 회원의 나이가 28 미만이면 이름을 "비회원"으로 수정
long count = queryFactory
.update(member)
.set(member.username, "비회원")
.where(member.age.lt(28))
.execute();
.update(qEntity)
.set(updateField, updateValue)
.where(predicate)
.execute();
더하기, 곱하기 연산
updateValue = updateField.add(x) //updateField+x
ex. set(member.age, member.age.add(1))
updateValue = updateField.multiply(y) //updateField*y
삭제 벌크 연산
long count = queryFactory
.delete(member)
.where(member.age.gt(18))
.execute();
주의: JPQL 배치와 마찬가지로, 영속성 컨텍스트에 있는 엔티티를 무시하고 실행되기 때문에 배치 쿼리를 실행하고 나면 영속성 컨텍스트를 초기화 하는 것이 안전하다. (DB 값 != 영속성 컨텍스트 값)
DB에서 엔티티를 조회할 때, 영속성 컨텍스트에 같은 엔티티(id 기준)가 있으면 새롭게 조회한 엔티티는 버려진다.