ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 심심해서 살펴본 Querydsl fetchOne() 구현
    Java & Kotlin 2023. 8. 27. 17:19
    반응형

    (이번 글은 깊은 내용이 없으므로 가볍게 읽어주세요)

    1) Querydsl이란?

    Querydsl은 JPA의 아쉬운 점을 보조하기 위해서 나왔습니다.

     

    JPA 명세를 구현한 ORM 프레임워크는 관계형 데이터베이스의 테이블 설계와 애플리케이션에서의 객체지향적인 설계가 맞지 않는 것을 해결하기 위해서 탄생되었는데요. 자바 코드를 활용하여 객체를 설계하고 테이블의 연관관계를 나타낼 수 있다는 것이 큰 장점입니다. 즉 테이블도 객체로서 바라보며 쿼리를 사용하고 조작할 수 있게 되었습니다.

     

    주로 사용되는 구현체인 Hibernate는 기본적으로 String 기반의 메서드명을 활용하여 쿼리(HQL)를 사용하는데요. 단순 조회, 저장 등은 여전히 간결하고 쉽습니다.

     

    하지만 여러개의 where절, join 등 발생하게 되면 메서드명이 길어지고 복잡해집니다. 즉 가독성이 떨어지는 문제가 있습니다. 또한 if문과 같은 분기문이 필요할 때는 service layer에서 모든 처리가 필요해집니다. 특히 String concatenation 기반으로 동작하는 HQL은 컴파일 시점에 오류를 알 수 없습니다.

     

    위의 아쉬운점을 보완하기 위해서 JPA의 Criteria API가 나왔는데요.

    EntityManager em = ...;
    CriteriaBuilder cb = em.getCriteriaBuilder();
    CriteriaQuery<Pet> cq = cb.createQuery(Pet.class);
    Root<Pet> pet = cq.from(Pet.class);
    cq.select(pet);
    TypedQuery<Pet> q = em.createQuery(cq);
    List<Pet> allPets = q.getResultList();

     

    컴파일 시점의 오류 탐지, 자바 코드를 활용하여 쿼리를 사용할 수 있다는 장점이 있습니다. 하지만 여전히 코드가 길고 가독성은 떨어집니다.

     

    위의 단점들을 보완하기 위하여 Querydsl framework가 나왔습니다. 자바 코드를 활용하여 타입 안정성을 가질 수 있습니다. 그리고 HQL과 다르게 실제 쿼리와 가깝게 작성할 수 있어서 가독성도 더욱 좋습니다.


    2) 그래서 fetchOne()은 왜 보는 거죠?

    Querydsl을 사용해 보신 분들의 대부분은 fetchOne() 사용법을 아실 것 같은데요.

    • 있다면, 단건 응답
    • 없다면, NULL 응답

     

    하지만 중요한 것은 조회 결과가 두 건 이상인 경우 NonUniqueResultException이 발생합니다.

     

    테이블 설계 시 중복을 방지하기 위하여 아주 엄격한 unique key를 걸 수도 있습니다. 그렇다면 걱정을 하지 않아도 되는데요. 하지만 설계에 따라 테이블에 key를 걸지 않을 수도 있고, 중복을 허용할 수도 있고, 애플리케이션 계층에서 처리할 수도 있습니다.

     

    여하튼 중복인 경우에 사용하면 NonUniqueResultException이 발생한다는 것인데요. Querydsl를 사용하시는 분들은 이미 알고 계신 사실일 것입니다.

     

    하지만 (바보 같은 저는) 테이블에 중복 데이터가 있다는 것을 인지하지 못하고 사용했다가 종종 문제를 만났습니다. 그리고 습관적으로 단건 조회 시 사용했었는데요.

     

    도대체 어떻게 동작하길래 NonUniqueResultException를 발생시키는 것인가?라는 호기심으로 파악해 봤습니다.


    3) fetchOne() 동작 방식

    • AbstractJPAQuery.class 사용
    • getSingleResult()를 활용하여 단건 조회

    • AbstractJPAQuery.class 사용
    • 단건 조회
      • 조회된 결과가 있다면 instance 생성
      • 없다면 null 리턴

    • first 변수에 조회한 첫 번째 데이터(엔티티 객체)의 담습니다
    • 조회한 데이터의 두 번째와 first 변수(첫 번째)를 비교합니다
    • 일치하지 않는다면 NonUniqueResultException를 발생시킵니다

     

    내구 구현을 확인했을 때 다건을 가정하고 List 타입으로 조회하고 있습니다. 하지만 조회한 데이터(엔티티 객체)의 주소를 비교합니다. 그리고 일치하지 않는다면, row가 2개라면, NonUniqueResultException을 발생시킵니다.

     

    다만 의문인 점은 row가 2개라면, 각각 맵핑되는 객체의 주소들은 다를 것입니다. 단순히 사이즈만 확인하여, 사이즈가 2 이상이라면 NonUniqueResultException을 발생시키게 하더라도 동일하게 동작할 것 같은데요. 여러 곳에서 사용되는 공개 메서드이기 때문에 다른 의도를 가지고 만들어졌을지도 궁금해집니다.


    4) 결론

    사실 테이블에 중복 데이터가 있다고 가정하여 fetchList(), orderBy + limit을 활용하면 해결되는 문제입니다. fetchOne() 메서드 자체에는 아무런 문제가 없습니다.

     

    다만 습관적으로 작성하는 잘못된 코드 습관과 인터페이스에서 설명하는 주석을 제대로 확인하는 습관이 문제였습니다.

     

    이번 계기로 라이브러리가 제공하는 것들이 무엇을 의도하는지 확인하고 사용하는 습관을 들여야 할 것 같습니다.


    5) 참고 문헌

    반응형

    댓글

Designed by Tistory.