본문 바로가기

웹 프로그래밍/JPA

Querydsl 중급

프로젝션: select의 대상 지정 (차원이 줄어듬)

List<String> result = quertFactory.select(member.username).from(member).fetch();

 

프로젝션 대상이 둘이상이면 튜플(가급적 리포지토리 계층안에서만 사용)이나 DTO로 조회(권장)

List<Tuple> result = quertFactory.select(member.username, member.age).from(member).fetch();


프로젝션과 결과반환 - DTO

순수 JPA에서 DTO 조회할때는 new 명령어로 패키지 명까지 적어줘야함(귀찮음)

 

Querydsl 빈생성 방식

 

-프로퍼티 접근(setter)

 

        List<MemberDto> result = queryFactory
                .select(Projections.bean(MemberDto.class,
                        member.username,
                        member.age))
                .from(member)
                .fetch();

 

-필드 직접 접근

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();

 

프로퍼티나, 필드 접근 생성방식에서 이름이 다를때 해결방안

        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();


프로젝션과 결과반환 - @QueryProjection 어노테이션 하나로 끝

 

   @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();

 

컴파일시 오류 검증 가능

단점: Q파일 생성해야함, Querydsl에 의존성을 가지게됨(구조적 문제)

 


동적쿼리

-BooleanBuilder 사용

 

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();

 

-Where 다중 파라미터 사용: 깔끔

 

private List<Member> searchMember2(String usernameCond, Integer ageCond) {

        return queryFactory
                .selectFrom(member)
                .where(usernameEq(usernameCond), ageEq(ageCond)) // where절 안에 null이 들어가면 무시됨 -> 동적쿼리!!
                .fetch();
    }

    private BooleanExpression usernameEq(String usernameCond) {
        if(usernameCond == null){
            return null;
        }
            return member.username.eq(usernameCond);

    }

 

또는


    private BooleanExpression allEq(String usernameCond, Integer ageCond){
        return usernameEq(usernameCond).and(ageEq(ageCond));
    }

->  where(allEq(usernameCond, ageCond)) // 메서드로 따로 뽑아 조립 가능

 

-null 체크 주의

-다른 쿼리에서도 메서드 재활용 가능

-쿼리 자체의 가독성 높아짐

-여러가지 조건이 조합이 가능(자바 코드)


수정, 삭제 배치 쿼리(벌크 연산)

-쿼리 한번으로 대량 데이터 수정

 

long count = queryFactory
                .update(member)
                .set(member.username, "비회원")
                .where(member.age.lt(28))
                .execute();

 

모든 벌크 연산은 영속성 컨텍스트를 무시함(DB는 바뀌나 1차 캐시는 그대로)

-> DB와 영속성 컨텍스트의 상태를 맞추기 위해 em.flush(), em.clear()를 해줘야함

 

-기본적인 연산 가능

 

long count = queryFactory
                .update(member)
                .set(member.age, member.age.add(1))
                .execute();

 

-삭제

 

        long count = queryFactory
                .delete(member)
                .where(member.age.gt(18))
                .execute();


SQL function 호출하기

 

        List<String> result = queryFactory
                .select(Expressions.stringTemplate(
                        "function('replace', {0}, {1}, {2})",
                        member.username, "member", "M"))
                .from(member)
                .fetch();

 

-간단한 함수는 querydsl에 내장 돼있음

member.username.upper()


사용자 정의 리포지토리

1. 사용자 정의 인터페이스 작성

2. 사용자 정의 인터페이스 구현

3. 스프링 데이터 리포지토리에 사용자 정의 인터페이스 상속


count 쿼리가 생략가능한 경우 생략해서 처리(최적화)

1. 페이지 시작이면서 컨텐츠 사이즈 < 페이지 사이즈 일때

2. 마지막 페이지 일때 ( offset + 컨텐츠 사이즈 = 전체 사이즈)

스프링 데이터 JPA가 아래처럼 간단히 기능을 제공

 

JPAQuery<Member> countQuery = queryFactory
                .select(member)
                .from(member)
                .leftJoin(member.team, team)
                .where(
                        usernameEq(condition.getUsername()),
                        teamNameEq(condition.getTeamName()),
                        ageGoe(condition.getAgeGoe()),
                        ageLoe(condition.getAgeLoe())
                );

 

return PageableExecutionUtils.getPage(content, pageable, () -> countQuery.fetchCount());


컨트롤러 개발

@GetMapping("/v2/members")
    public Page<MemberTeamDto> searchMemberV2(MemberSearchCondition condition, Pageable pageable){
        return memberRepository.searchPageSimple(condition, pageable);
    }

그냥 이렇게 파라미터에 Pageable만 추가하면 Controller 바인딩 될때 자동으로 값이 들어감

 

즉, Postman에서 http://localhost:8080/v2/members?page=0&size=5 호출하면 자동으로 pageable에 바인딩


 

'웹 프로그래밍 > JPA' 카테고리의 다른 글

Querydsl  (2) 2024.02.23
연관관계 편의 메서드  (0) 2024.01.15
Querydls 기본  (3) 2023.03.21
스프링 데이터 JPA  (0) 2023.03.18
JPA 활용(2) 팁 정리  (0) 2023.03.16