Querydsl - 기본 문법(1)

YulHee Kim·2022년 1월 4일
0

Querydsl

목록 보기
2/6

김영한님의 '실전! Querydsl'을 수강하며 정리하는 글입니다

예제 도메인 모델

✔️예제 도메인 모델

엔티티 클래스

Member 엔티티

package study.querydsl.entity;

import lombok.*;

import javax.persistence.*;

@Entity
@Getter @Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@ToString(of = {"id", "username", "age"})
public class Member {

    @Id @GeneratedValue
    @Column(name = "member_id")
    private Long id;
    private String username;
    private int age;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "team_id")
    private Team team;

    public Member(String username) {
        this(username, 0);
    }

    public Member(String username, int age) {
        this(username, age, null);
    }

    public Member(String username, int age, Team team) {
        this.username = username;
        this.age = age;
        if (team != null) {
            changeTeam(team);
        }
    }

    private void changeTeam(Team team) {
        this.team = team;
        team.getMembers().add(this);
    }


}
  • @NoArgsConstructor AccessLevel.PROTECTED 어노테이션은 기본 생성자의 접근 제어를 PROTECTED로 열어놓음으로써 무분별한 객체 생성을 막을 수 있으며, JPA 스펙상 PROTECTED로 열어두어야 합니다.
  • @ToString은 가급적 연관관계가 없는 필드만 사용해서 정의해야합니다. 무한 반복 때문에 에러가 발생할 수 있기 때문입니다.
  • changeTeam()은 연관관계 편의 메소드로 양방향 연관관계를 한번에 처리합니다.

Team 엔티티

package study.querydsl.entity;

import lombok.*;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import java.util.ArrayList;
import java.util.List;

@Entity
@Getter @Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@ToString(of = {"id", "name"})
public class Team {

    @Id @GeneratedValue
    private Long id;
    private String name;

    @OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<>();

    public Team(String name) {
        this.name = name;
    }
}

✔️ 동작 확인

데이터 확인 테스트

package study.querydsl.entity;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Commit;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityManager;

import java.util.List;

import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
@Transactional
class MemberTest {

    @Autowired
    EntityManager em;

    @Test
    public void testEntity() {
        Team teamA = new Team("teamA");
        Team teamB = new Team("teamB");
        em.persist(teamA);
        em.persist(teamB);

        Member member1 = new Member("member1", 10, teamA);
        Member member2 = new Member("member2", 20, teamA);

        Member member3 = new Member("member3", 30, teamB);
        Member member4 = new Member("member4", 40, teamB);
        em.persist(member1);
        em.persist(member2);
        em.persist(member3);
        em.persist(member4);

        //초기화
        em.flush();
        em.clear();

        //확인
        List<Member> members = em.createQuery("select m from Member m", Member.class)
                .getResultList();

        for (Member member : members) {
            System.out.println("member = " + member);
            System.out.println("-> member.team" + member.getTeam());
        }
    }

}

기본 문법

✔️JPQL vs Querydsl

테스트 코드로 비교해보겠습니다.

@SpringBootTest
@Transactional
public class QuerydslBasicTest {

    @Autowired
    EntityManager em;

    JPAQueryFactory queryFactory;

    @BeforeEach
    public void before() {
        queryFactory = new JPAQueryFactory(em);
        Team teamA = new Team("teamA");
        Team teamB = new Team("teamB");
        em.persist(teamA);
        em.persist(teamB);

        Member member1 = new Member("member1", 10, teamA);
        Member member2 = new Member("member2", 20, teamA);

        Member member3 = new Member("member3", 30, teamB);
        Member member4 = new Member("member4", 40, teamB);
        em.persist(member1);
        em.persist(member2);
        em.persist(member3);
        em.persist(member4);
    }

    @Test
    public void startJPQL() {
        //member1을 찾아라
        String qlString =
                "select m from Member m where m.username = :username";

        Member findMember = em.createQuery(qlString, Member.class)
                .setParameter("username", "member1")
                .getSingleResult();

        assertThat(findMember.getUsername()).isEqualTo("member1");
    }

    @Test
    public void startQuerydsl() {
        Member findMember = queryFactory
                .select(member)
                .from(member)
                .where(member.username.eq("member1"))
                .fetchOne();

        assertThat(findMember.getUsername()).isEqualTo("member1");
    }

}

querydsl을 사용하기 위해선 em으로 JPAQueryFactory를 생성해야합니다.

  • Querydsl은 JPQL의 빌더입니다.
  • JPQL은 오타가 났을 경우 실행 시점에서 오류가 나지만, Querydsl은 컴파일 시점에서 오류가납니다.
  • JPQL은 파라미터 바인딩을 직접하고, Querydsl은 파라미터 바인딩을 자동 처리합니다.

✔️기본 Q-Type 활용

Q클래스 인스턴스를 사용하는 2가지 방법

QMember qMember = new QMember("m"); //별칭 직접 지정
QMember qMember = QMember.member; //기본 인스턴스 사용

기본 인스턴스 사용을 권장하지만 코드가 좀 길어지는 경향이 있으므로, 기본 인스턴스를 static import와 함께 사용하면 코드가 깔끔해집니다. 위 예제 코드가 static import를 하여 코드를 작성한 경우입니다.

그리고 다음 설정을 추가하면 실행되는 JPQL을 볼 수 있습니다.
spring.jpa.properties.hibernate.use_sql_comments: true

✔️검색 조건 쿼리

기본 검색 쿼리

    @Test
    public void search() {
        Member findMember = queryFactory
                .selectFrom(member)
                .where(member.username.eq("member1")
                        .and(member.age.eq(10)))
                .fetchOne();

        assertThat(findMember.getUsername()).isEqualTo("member1");
    }

검색 조건은 .and(), or()를 메서드 체인으로 연결할 수 있습니다.
또한 select, from을 selectFrom으로 합칠 수 있습니다.

JPQL이 제공하는 모든 검색 조건 제공

member.username.eq("member1") //username = 'member1'
member.username.ne("member1") //username != 'member1'
member.username.eq("member1").not() //username != 'member1'

member.username.isNotNull() //이름이 is not null

member.age.in(10,20)
member.age.notIn(10,20)
member.age.between(10,30)

member.age.goe(30) // age >= 30
member.age.gt(30) // age > 30
member.age.loe(30) // age <= 30
member.age.lt(30) // age < 30

member.username.like("member%") // like 검색
member.username.contains("member") // like '%members%' 검색
member.username.startsWith("member") // like 'members%' 검색

And 조건을 파라미터로 처리

    @Test
    public void searchAndParam() {
        Member findMember = queryFactory
                .selectFrom(member)
                .where(
                        member.username.eq("member1"),
                        member.age.eq(10)
                )
                .fetchOne();

        assertThat(findMember.getUsername()).isEqualTo("member1");
    }

where()에 파라미터로 검색조건을 추가하면 AND조건이 추가됩니다.
이 경우 null값은 무시하는데 메서드 추출을 활용해서 동적 쿼리를 깔끔하게 만들 수 있습니다.

✔️결과 조회

  • fetch() : 리스트 조회, 데이터 없으면 빈 리스트 반환
  • fetchOne() : 단 건 조회
    • 결과가 없으면 : null
    • 결과가 둘 이상이면 : com.querydsl.core.NonUniqueResultException 에러발생
  • fetchFirst(): limit(1).fetchOne()
    - fetchResults(): 페이징 정보 포함, total count 쿼리 추가 실행
    - fetchCount(): count쿼리로 변경해서 cout 수 조회

하지만 fetchResults(), fetchCount()가 deprecated되었습니다. 이전 버전에서는 위 메소드를 이용하여 페이징 쿼리를 작성했지만 count쿼리가 모든 다중 그룹 쿼리에서 완벽하기 지원되지 않기 때문에 Querydsl 5.0.0 API부터 fetch 사용을 권장하고 있기 때문입니다. 그러므로 count정도는 자바에서 처리하면 되겠습니다.

예제

import static com.example.querydslexample.entity.QUser.user;

public Page<User> findUserWithPaging(Pageable pageable) {

	List<User> content = queryFactory
			.selectFrom(user)
			.where(user.username.like("user_"))
			.offset(pageable.getOffset()) // offset
			.limit(pageable.getPageSize()) // limit
			.fetch();

	return new PageImpl<>(content, pageable, content.size()); // 쿼리 결과로 페이징 객체 리턴
}

조회 예제

    @Test
    public void resultFetch() {
        List<Member> fetch = queryFactory
                .selectFrom(member)
                .fetch();

        Member fetchOne = queryFactory
                .selectFrom(member)
                .fetchOne();

        Member fetchFirst = queryFactory
                .selectFrom(member)
                .fetchFirst();
    }

참고)
https://devwithpug.github.io/java/querydsl-with-datajpa/

profile
백엔드 개발자

0개의 댓글