기존에 활용해 보았던 Querydsl에 대하여 추가적으로 알게 된 기능들을 정리해 본다.
Dto를 Q타입으로 사용하는 방법(@QuertProjection)
@Data
public class MemberTeamDto {
private Long memberId;
private String username;
private int age;
private Long teamId;
private String teamName;
@QueryProjection
public MemberTeamDto(Long memberId, String username, int age, Long teamId, String teamName) {
this.memberId = memberId;
this.username = username;
this.age = age;
this.teamId = teamId;
this.teamName = teamName;
}
}
위 방법은 데이터를 조회하여 바로 Dto 객체로 만들어 반환할 수 있게 해 준다. 기존 JPQL의 문법에서도 문자열의 생성자 호출을 통해 Dto로 변환이 가능하였지만 Q타입은 컴파일 시 해당 코드에 문제가 있는지 확인이 가능하여 사용하기 편하다.
만약 Dto와 Entity의 필드의 명칭이 다르다면 .as 를 통하여 어떤 필드에 값을 넣어 생성할 것인지 명시하여야 한다.
** 사용 시 주의점 **
위 방법으로 Dto 계층으로의 변환은 쉬워지지만 아키텍처상 Dto가 Querydsl이라는 라이브러리를 의존해야 한다는 단점이 존재한다. 만약 Querydsl을 사용하지 않도록 변경하거나 한다면 Dto계층에도 수정이 일어날 수 있다는 점이 단점이다.
만약 Dto와의 의존성이 꺼려진다면 아래와 같은 방법도 고려해 볼 수 있다.
//세터로 값 할당
@Test
public void findDtoBySetter() throws Exception {
//given
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);
}
}
//필드에 직접 값 할당
@Test
public void findDtoByField() throws Exception {
//given
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);
}
}
//필드와 세터는 엔티티와 Dto 의 필드가 매칭되어야 값이 할당됨, as로 Dto의 필드를 지정할 수 있음
@Test
public void findDtoByConstructor() throws Exception {
//given
List<MemberDto> result = queryFactory
.select(Projections.constructor(MemberDto.class,
member.username,
member.age))
.from(member)
.fetch();
List<UserDto> UserResult = queryFactory
.select(Projections.constructor(UserDto.class,
member.username,
member.age))
.from(member)
.fetch();
for (MemberDto memberDto : result) {
System.out.println("memberDto = " + memberDto);
}
for (UserDto userDto : UserResult) {
System.out.println("userDto = " + userDto);
}
}
@Test
public void findUserDto() throws Exception {
//given
QMember sub = new QMember("sub");
List<UserDto> userResult = queryFactory
.select(Projections.fields(UserDto.class,
member.username.as("name"),
member.age))
.from(member)
.fetch();
List<UserDto> userResult2 = queryFactory
.select(Projections.fields(UserDto.class,
member.username.as("name"),
ExpressionUtils.as(JPAExpressions
.select(sub.age.max())
.from(sub), "age")))
.from(member)
.fetch();
//when
for (UserDto userDto : userResult) {
System.out.println("userDto = " + userDto);
}
for (UserDto userDto : userResult2) {
System.out.println("userDto2 = " + userDto);
}
//then
}
연관관계없는 테이블 간의 Join
/**
* 연관관계 없는 엔티티 외부 조인
* 회원 이름과 팀이름이 같은 대상 조인
*/
@Test
public void join_notRelation() throws Exception {
//given
em.persist(new Member("teamA"));
em.persist(new Member("teamB"));
em.persist(new Member("teamC"));
//when
List<Tuple> result = queryFactory
.select(member, team)
.from(member)
.leftJoin(team) //조인하는 테이블의 연관 엔티티가 들어가는것이 아닌 관계 없는 엔티티 Q타입이 들어가고 on 절에서 조건 정의
.on(member.username.eq(team.name)) // 연관관계가 없음으로 어떤 필드값끼리 매칭할지 정해준다.
.fetch();
//then
for (Tuple tuple : result) {
System.out.println("tuple = " + tuple);
}
}
페이지네이션 시 사용법
@Override
public Page<MemberTeamDto> searchPageComplex(MemberSearchCondition cond, Pageable pageable) {
List<MemberTeamDto> result = queryFactory.
select(new QMemberTeamDto(
member.id.as("memberId"),
member.username,
member.age,
team.id.as("teamId"),
team.name.as("teamName")))
.from(member)
.leftJoin(member.team, team)
.where(usernameEq(cond.getUsername()), teamNameEq(cond.getTeamName()), ageGoe(cond.getAgeGoe()), ageLoe(cond.getAgeLoe()))
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();
JPAQuery<Long> count = queryFactory
.select(member.count())
.from(member)
.leftJoin(member.team, team)
.where(usernameEq(cond.getUsername()), teamNameEq(cond.getTeamName()), ageGoe(cond.getAgeGoe()), ageLoe(cond.getAgeLoe()));
return PageableExecutionUtils.getPage(result, pageable, count::fetchFirst);
}
강의에서는 fetchCount를 사용해 총데이터에 대한 카운트를 얻어왔는데 현재 기준으로 fetchResults와 fetchCount Deprecated 상태여서 위와 같이 작성하였다. 페이지 조회에 대한 쿼리와 카운트 쿼리가 두 번 나가지만 실제로 카운트 쿼리는 다르게 내보내야 할 경우가 많다고 하여 위와 같이 쓰는 게 가장 좋은 방법일 것 같다.
참고자료
김영한 : 실전! Querydsl
https://www.inflearn.com/course/querydsl-%EC%8B%A4%EC%A0%84/dashboard
실전! Querydsl | 김영한 - 인프런
김영한 | Querydsl의 기초부터 실무 활용까지, 한번에 해결해보세요!, 복잡한 쿼리, 동적 쿼리는 이제 안녕! Querydsl로 자바 백엔드 기술을 단단하게. 🚩 본 강의는 로드맵 과정입니다. 본 강의는 자
www.inflearn.com
'JPA' 카테고리의 다른 글
Querydsl 서브쿼리 (0) | 2024.03.31 |
---|---|
JPA , QueryDsl 수정 중 에러 해결 (0) | 2024.03.17 |
영속성 컨텍스트 (0) | 2024.01.28 |
JPQL문법 2 (페치 조인,벌크 연산) (0) | 2022.12.23 |
JPQL 문법 1 (1) | 2022.12.22 |