[인강노트 - querydsl] 4-1. 기본 문법 1편

봄도둑·2022년 12월 27일
0

김영한님의 실전! querydsl 강의 내용을 정리한 노트입니다. 블로그에 있는 자료를 사용하실 때에는 꼭 김영한님 강의 링크를 남겨주세요!

※assertions 검증 결과는 어떻게 확인할까?

  • assertions를 이용해 검증 시 같은 값이 나올 때 → 그냥 실행시킨 테스트 코드가 정상 동작하면 됨

  • assertions 검증 실패 시(다른 값일 때) → expected : 비교 기준 값( == ‘member2’), actual : 실제 들고 있는 값(result.getUsername() == )

1. JPQL vs Querydsl

  • 동작을 비교할 테스트 코드
    @SpringBootTest
    @Transactional
    public class QuerydslBasicTest {
    
        @Autowired
        EntityManager em;
    
        @BeforeEach //테스트 실행 전 데이터를 넣는 작업 진행
        public void before() {
            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() {
            //memeber 1 찾기
            Member findMember = em.createQuery("select m from Member m where m.username = :username", Member.class)
                    .setParameter("username", "member1")
                    .getSingleResult();
    
            Assertions.assertThat(findMember.getUsername()).isEqualTo("member1");
        }
    
        @Test
        public void startQuerydsl() {
            JPAQueryFactory queryFactory = new JPAQueryFactory(em);
            QMember m = new QMember("m");
    
            Member findMember = queryFactory
                    .select(m)
                    .from(m)
                    .where(m.username.eq("member1"))
                    .fetchOne();
    
            Assertions.assertThat(findMember.getUsername()).isEqualTo("member1");
        }
    }
  • 먼저 Querydsl을 살펴 보자
    @Test
        public void startQuerydsl() {
            JPAQueryFactory queryFactory = new JPAQueryFactory(em);
            QMember m = new QMember("m"); //QMember의 인자에는 별칭을 넣어줌 -> 크게 중요하지 않음. 앞으로 안 쓸 거임ㅋㅋ
    
            // 파라미터 바인딩 없이 사용 가능
            //m.username.eq("member1") 으로 써도 prepare statement로 파라미터 바인딩이 자동으로 진행
            //db 입장에서도 성능면에서 유리
            Member findMember = queryFactory
                    .select(m)
                    .from(m)
                    .where(m.username.eq("member1"))
                    .fetchOne();
    
            Assertions.assertThat(findMember.getUsername()).isEqualTo("member1");
        }
    • JPAQueryFactory를 생성할 때 EntityManager를 인자로 넘겨준다

    • 별도의 파라미터 바인딩이 없음 → prepare statement로 파라미터 바인딩이 자동으로 진행, 이러면 db 입장에서는 성능면에서도 유리함

    • 요 JPAQueryFactory는 필드로 뺄 수 있음 → 그런데 동시성 문제가 있을텐데???? spring에서 제공하는 Manager들은 멀티 스레드에 맞게 설계가 되어 있기 때문에 동시성 문제에 대해 크게 고민하지 않아도 됨. 트렌젝션에 바인딩 되도록 분배해줌

      @SpringBootTest
      @Transactional
      public class QuerydslBasicTest {
      
          @Autowired
          EntityManager em;
      
          JPAQueryFactory queryFactory;
      
          @BeforeEach //테스트 실행 전 데이터를 넣는 작업 진행
          public void before() {
              queryFactory = new JPAQueryFactory(em);
              //db에 값 넣어주는 로직 ...
          }
      
          @Test
          public void startQuerydsl() {
              QMember m = new QMember("m");
      
              Member findMember = queryFactory
                      .select(m)
                      .from(m)
                      .where(m.username.eq("member1"))
                      .fetchOne();
      
              Assertions.assertThat(findMember.getUsername()).isEqualTo("member1");
          }
      }
  • JPQL은 String query에 오타가 생겼을 때 컴파일 시점에서는 오류를 잡아낼 수 없음(말 그대로 문자열이기 때문에) → 오류가 있는 string query를 날려줄 때, 즉 해당 메소드가 호출되는 순간 오류를 뿜음(런타임 오류)
  • querydsl은 큐타입으로 인해 오타가 발생하면 컴파일 시점에서 오류를 잡아냄(큐 타입을 통해 자바 코드로 개발할 수 있기 때문→결국 쿼리가 자바의 문법을 따라가도록 되어 있기 때문에 자바 컴파일러가 오류를 잡아줌)
  • 자바로 개발하기 때문에 코드 어시스턴스를 편하게 받을 수 있음(자동 완성 같은 거…)

2. 기본 Q-type 활용

  • 큐 타입의 사용법
QMember qMember = new QMember("m"); //별칭 직접 지정
QMember qMember = QMember.member; //기본 인스턴스 사용
  • 그런데 QMember.member를 static import 해서 사용할 수 있음 → intelij alt + enter 후 static import 선택
@Test
    public void startQuerydsl() {
        Member findMember = queryFactory
                .select(member)
                .from(member)
                .where(member.username.eq("member1"))
                .fetchOne();

        Assertions.assertThat(findMember.getUsername()).isEqualTo("member1");
    }
  • querydsl은 결과적으로 JPQL의 빌더 역할을 함 → 작성한 코드는 결과적으로 JPQL이 된다고 생각하면 됨
  • 그렇다면 JQPL의 동작을 보려면?
    • yml에 spring.jpa.properties.hibernate.use_sql_comments: true 설정을 추가하면 됨
  • QMember의 별칭 설정은 같은 테이블을 조인하는 경우에만 선언해서 사용 → 그렇지 않은 경우에는 그냥 static import 사용

3. 검색 조건 쿼리

  • 코드로 살펴보자!
@Test
public void search() {
    //selectFrom = select + from을 합친 것
    //이름이 member1이면서(and) 나이가 10인 사람을 조회해
    //조건의 체인은 and, or 다 걸 수 있음
    Member findMember = queryFactory
            .selectFrom(member)
            .where(member.username.eq("member1").and(member.age.eq(10)))
            .fetchOne();

    Assertions.assertThat(findMember.getUsername()).isEqualTo("member1");
}
  • 쿼리는 이렇게 나감
  • 조건을 달 수 있는 것들은 다음과 같이 쓸 수 있음 → 사실 몰라도 어시스턴스의 도움을 받아서 찾을 수 있음. 굳이 외울 필요가 없다는 것
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) // age in (10,20)
member.age.notIn(10, 20) // age not in (10, 20)
member.age.between(10,30) //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 ‘%member%’ 검색
member.username.startsWith("member") //like ‘member%’ 검색
  • where의 파라미터는 여러 개 넘기면 and로 인식됨 → 요렇게 파라미터로 넘길 경우 null이 들어갔을 때 null을 무시함 → 동적 쿼리 만들 때 기가 막히다는 것
    • ex) where(member.username.eq("member1", member.age.eq(10), null)
//and 조건은 쉼표로 끊어 갈 수 있음
//where의 파라미터를 여러 개 넘기면 걔가 and로 붙음
Member findMember = queryFactory
        .selectFrom(member)
        .where(
                member.username.eq("member1"),
                member.age.eq(10)
        )
        .fetchOne()

4. 결과 조회

  • fetch() : 리스트 조회 → 가장 많이 사용(데이터가 없다면 빈 리스트를 반환)

  • fetchOne() : 단 건 조회 → 결과가 없으면 null, 결과가 2개 이상이면 com.querydsl.core.NonUniqueResultException

  • fetchFirst() : limit(1).fetchOne()와 같음

  • fetchResults() : 페이징 정보 포함, total count 쿼리 추가 실행 → 실제 나가는 쿼리는 2개

    • 얘는 현재 deprecated된 상태 → 찾아보니 복잡한 다중 쿼리에서 잘 동작하지 않기 때문에 deprecated 처리 했다고 함 → 성능 이슈로 데이터를 가져오는 쿼리와 카운트 쿼리가 다른 쿼리로 나가는 경우가 있다고 함. 나오는 결과가 다를 수 있음. 성능을 중요하게 여기는 경우 쿼리를 따로 쏴줘야 함
    • 페이징 처리에 대한 쿼리는 fetch 후 따로 자바 레벨에서 따로 count 처리 하라고 함
  • fetchCount() : count 수 조회

    • 얘도 fetchResults와 마찬가지로 deprecated됨(querydsl 5.0 버전부터)
    • fetch().size()로 처리하는 걸 권장함 → 물론 서비스 규모에 따라 적절한 count 수를 받아올 필요는 있음
  • 사용 코드는 다음과 같음

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

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

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

    QueryResults<Member> results = queryFactory
            .selectFrom(member)
            .fetchResults();
    long total = results.getTotal();
    List<Member> content = results.getResults();

    long totalCount = queryFactory
            .selectFrom(member)
            .fetchCount();
}

5. 정렬

  • 코드로 바로 보자!
/**
 * 회원 정렬 순서
 * 1. 회원 나이 내림차순(desc)
 * 2. 회원 이름 올림차순(asc)
 * 단, 2에서 회원 이름이 없으면 마지막에 출력(nulls last)
 */
@Test
public void sort() {
    //예제를 위해 데이터를 더 추가하는 작업
    em.persist(new Member(null, 100));
    em.persist(new Member("member5", 100));
    em.persist(new Member("member6", 100));
		
		//나이 조건은 다 100살로 맞췄기 때문에 실질적으로는 회원 이름의 올림차순만 적용
    List<Member> results = queryFactory
            .selectFrom(member)
            .where(member.age.eq(100))
            .orderBy(member.age.desc(), member.username.asc().nullsLast())
            .fetch();

    Member member5 = results.get(0);
    Member member6 = results.get(1);
    Member memberNull = results.get(2);

    Assertions.assertThat(member5.getUsername()).isEqualTo("member5");
    Assertions.assertThat(member6.getUsername()).isEqualTo("member56");
    Assertions.assertThat(memberNull.getUsername()).isNull();
}

6. 페이징

  • 코드로 바로 보자!
@Test
public void paging1() {
    List<Member> result = queryFactory
            .selectFrom(member)
            .orderBy(member.username.desc())
            .offset(1)
            .limit(2)
            .fetch();

    Assertions.assertThat(result.size()).isEqualTo(2);
}
  • 쿼리는 요렇게 나감

    • 신기한 건 offset과 limit를 쏴주면 각 db의 방언에 따라 쿼리가 다르게 나감 → 현재 사용하고 있는 db는 mariaDB이기 때문에 mysql의 방언으로 나감
profile
Java Spring 백엔드 개발자입니다. java 외에도 다양하고 흥미로운 언어와 프레임워크를 학습하는 것을 좋아합니다.

0개의 댓글