- 최종프로젝트 10일차, 내가 맡은 상품쪽 로직을 작성하다가 Query문을 수정할 상황이 발생했다.
public List<ItemResponse> searchItem(String itemName) { List<Item> itemList = itemRepository.findAll(); List<ItemResponse> itemResponseList = new ArrayList<>(); for (Item item : itemList) { if (item.getName().contains(itemName)) { itemResponseList.add(new ItemResponse(item)); } } return itemResponseList; }
- 기존의 상품 검색 로직이다.
- DB에 존재하는 모든 상품을 불러오고
- 그 목록에서 검색어가 포함된 상품만을 반환해주고 있다.
- 검색어와 상관없이 검색을 수행할때 항상 모든 상품을 불러오는것이 굉장히 비효율적으로 보였다.
- DB에 접근하여 Query문을 통해 검색어를 포함한 상품만을 불러오고 싶었다.
+추가로 검색어로 시작하는 상품만을 불러오고 싶었다. (구글 검색창 처럼)
- 방법을 찾아보던중, JPQL과 QueryDSL을 찾게되었다.
- JPQL은 JPA의 일부로 Query를 Table이 아닌 객체(=엔티티) 기준으로 작성하는 객체지향 쿼리 언어라고 정의할 수 있다.
- JPQL은 객체를 기준으로 모든 것이 움직이기 때문에 개발할 때, Table에 매핑되는 객체가 반드시 존재해야 하며 당연하게도 검색할 때도 Table이 아닌 객체를 대상으로 검색해야 한다.
- JPQL의 특징
- SQL을 추상화한 JPA의 객체지향 쿼리
- Table이 아닌 Entity 객체를 대상으로 개발
- Entity와 속성은 대소문자 구분 (PERSON <> person)
- 별칭(alias) 사용 필수
- JPQL을 실제로 구현하는 방식에는 크게 두 가지 방식이 있다.
- EntityManager
@Autowired EntityManager em; TypedQuery<User> tq = em.createQuery("select p from Person p where p.FirstName = :firstName and p.LastName = :lastName", Person.class); tq.setParameter("firstName", "tablenext"); tq.setParameter("lastName", "kim"); > List<Person> personList = tq.getResultList();
- repository interface
public interface PersonRepository extends JpaRepository<Person, Long>{ /* 변수 바인딩 시, ?시퀀스 사용하는 경우 */ @Query("select p from Person p where p.firstName = ?1 and p.lastName = ?2") Person findPerson(String firstName, String lastName); /* 변수 바인딩 시, :이름 사용하는 경우 */ @Query("select p from Person p where p.firstName = :firstName and p.lastName = :lastName") Person findPerson2(@Param("firstName") String firstName, @Param("lastName") String lastName); }
- 두 가지 방식은 구현 방식에 있어서 차이점이 명확하지만,
공통점이자 문제점으로 쿼리를 String 형태로 작성하고 있다.
- JPQL의 문제점을 정리하면 다음과 같다.
- JPQL의 쿼리는 문자열(=String) 형태이기 때문에 개발자 의존적 형태
- Compile 단계에서 Type-Check가 불가능
- RunTime 단계에서 오류 발견 가능 (장애 risk 상승)
- 위의 JPQL의 문제점을 보완하기 위해 나온 것이 QueryDSL이다.
- QueryDSL은 정적 타입을 이용해서 SQL, JPQL을 코드로 작성할 수 있도록 도와주는 오픈소스 빌더 API이다.
- QueryDSL을 사용하는 목적은 뚜렷하다.
- 기존 방식(Mybatis, JPQL, etc..)은 모두 문자열(=String) 형태로 쿼리가 작성되었고 이로 인해 Compile 단계에서 Type-Check 불가했다.
- 이러한 risk를 줄이기 위해 QueryDSL이 등장했고 이를 통해 Compile 단계에서 Type-check가 가능해진 것이다.
- QueryDSL을 사용하는 방법은 다음과 같다.
- QueryDSL은 모든 쿼리에 대한 내용이 함수 형태로 제공된다.
- 그렇기 때문에 오류가 존재할 시, Compile 단계에서 바로 확인 가능하며 이에 따른 후속 조치가 가능하기 때문에 그만큼 risk가 줄어들게 되는 것이다.
- QueryDSL의 특징
- 문자가 아닌 코드로 작성
- Compile 단계에서 문법 오류를 확인 가능
- 코드 자동 완성 기능 활용 가능
- 동적 쿼리 구현 가능
- QueryDSL을 통해 검색을 구현하면서, 고민이 생겼다.
- 검색을 하는 방법으로 이름검색과 카테고리 검색이 있는데,
- 둘중 하나만 입력해도 작동이 되도록 구현하고 싶었다.
- 처음에는, 이름을 통한 검색과 카테고리를 통한 검색 API를 분리해야 하나 고민해보았고,
API를 통합하되, 케이스를 나누어서 Query문을 3가지로 작성해야되나 고민해보았다.- 고민해보면서 알게된게 바로 Boolean Expression이다.
- Boolean Expression은 다음과 같다.
- 코드를 보면 단순한 조건에 따라 반환을 해주는것으로 보이지만,
BooleanExpression은 null 반환 시 자동으로 조건절에서 제거 된다.
- 위의 코드를 보면 qItem.name.like(itemname + "%") 처럼 like문을 리턴해주고있다.
- like문은 한마디로 정리하자면 Java의 equals와 비슷하다.
- like(str)은 쿼리가 나갈 때 str자체가 나가기 때문에 정확하게 일치해야한다.
- 이를 통해 %연산을 선택할 수 있다. (itemname+"%"의 경우 itemname으로 시작하는 문자열)
- 나는 구글의 검색창처럼 itemname으로 시작하는 상품을 조회하고자 하여 itemname+"%"로 구성하였다.
- Contains는 Java의 contains와 유사하다.
- contains(str)은 쿼리가 나갈 때 %str%가 나간다.
프로젝트의 일일회고를 작성할 시간이 없을때가 있어서 그날그날 발생한 일에 대해 글을 적는것은 힘들것으로 보인다.
매일매일 그날의 일을 기록하기 보단, 내용을 기록하는것에 초점을 두려고 한다.
어제는 하루종일 ECS를 통한 서비스 배포와 CI/CD를 적용해보려고 시도했는데,
모르는 개념들이 매우 많아서(VPC, Nat 게이트웨이, 서브넷, 라우팅테이블, 등등..) 결국 배포에 실패했다.
나중에 이에 대해서도 공부해보고, 왜 실패하였는가, 그리고 성공해보려고 한다.
아직은 AWS친구들이 익숙치 않은것 같다.