kinggora 2022. 7. 14. 17:52

프로젝션과 결과 반환 - 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 기준)가 있으면 새롭게 조회한 엔티티는 버려진다.