개인 프로젝트를 하던 중 검색에 따라서 list의 출력 값이 나오게 하고 싶었다. 그래서 JPA를 이용한 조건절 구현에 대해서 찾아보았고 이를 정리하고 싶었다.
EntityManager를 통해 JPQL을 실행하려면, createQuery 메서드에 JPQL 쿼리 문자열을 전달하면 된다. 이는 Query 인스턴스를 반환하며, 이 인스턴스의 getResultList 또는 getSingleResult 메서드를 호출하여 쿼리 결과를 얻을 수 있다.
String jpql = "SELECT p FROM Post p WHERE p.title = :title AND p.content = :content";
Query query = entityManager.createQuery(jpql);
query.setParameter("title", "Hello");
query.setParameter("content", "World");
List<Post> result = query.getResultList();
장점
객체 지향적인 쿼리: JPQL은 SQL과 비슷하지만, 객체 지향적인 쿼리를 작성할 수 있다. 따라서 개발자는 데이터베이스 테이블이 아닌 자바 클래스와 그 관계에 집중할 수 있다.
데이터베이스 중립적: JPQL은 특정 데이터베이스에 종속되지 않는다. 즉, 같은 JPQL 쿼리를 다양한 데이터베이스에서 사용할 수 있다.
타입 안전성: JPQL은 컴파일 시점에 문법 오류를 찾아낼 수 있다. 이는 실행 시점에 발생할 수 있는 오류를 미리 방지하는 데 도움이 된다.
단점
복잡한 쿼리 작성 어려움: JPQL은 SQL보다 간단한 쿼리를 작성하는 데 유리하지만, 조인, 서브 쿼리 등 복잡한 쿼리를 작성하는 데는 한계가 있다.
성능 최적화 어려움: JPQL은 SQL을 추상화한 것이므로, 때때로 SQL에 직접 접근하는 것보다 성능 최적화가 어려울 수 있다.
동적 쿼리 작성 어려움: 문자열로 쿼리를 작성하는 방식이므로, 동적으로 쿼리를 생성하거나 수정하는 것이 복잡하고 오류 발생 가능성이 높다.
JPA의 Criteria API를 사용하여 조건문을 처리할 수 있다. Post라는 Entity를 가지고 예시를 들어보겠다. 가령, title이 특정 문자열을 포함하는 Post를 찾고 싶은 경우, 아래와 같이 작성할 수 있다.
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Post> cq = cb.createQuery(Post.class);
Root<Post> post = cq.from(Post.class);
Predicate titlePredicate = cb.like(post.get("title"), "%yourTitle%");
cq.where(titlePredicate);
TypedQuery<Post> query = entityManager.createQuery(cq);
List<Post> result = query.getResultList();
장점: JPA의 기본 기능을 사용하기 때문에 추가적인 라이브러리 없이 사용할 수 있다. 또한, SQL에 익숙한 사용자는 비교적 쉽게 사용할 수 있다.
단점: 코드가 복잡하고 가독성이 떨어진다. 또한, Criteria API는 JPA 2.0부터 지원되므로, 이전 버전의 JPA를 사용하는 경우 사용할 수 없다.
Specification
은 Spring Data JPA에서 제공하는 인터페이스로, 동적으로 쿼리를 생성하기 위한 명세를 정의하는 데 사용된다.
Spring Data JPA에서는 Specification을 사용하여 동적 쿼리를 작성할 수 있다. Specification은 Predicate를 생성하는 로직을 캡슐화한 것으로, JpaSpecificationExecutor 인터페이스를 상속받은 Repository에서 사용할 수 있다.
public class PostSpecification {
public static Specification<Post> titleContains(String title) {
return (root, query, criteriaBuilder) -> criteriaBuilder.like(root.get("title"), "%" + title + "%");
}
}
// Repository에서 사용하는 예시
List<Post> result = postRepository.findAll(PostSpecification.titleContains("yourTitle"));
장점: 코드가 간결하고 가독성이 좋다. 또한, Specification을 재사용할 수 있어 유지보수성이 좋다.
단점: Spring Data JPA에 의존성이 있다. 따라서, Spring Data JPA를 사용하지 않는 환경에서는 사용할 수 없다.
QueryDSL
은 동적으로 SQL, JPQL, JDOQL, 이외에도 다양한 쿼리를 생성하고 실행할 수 있는 프레임워크이다. QueryDSL은 타입 세이프한 쿼리를 작성할 수 있도록 도와준다. 즉, 쿼리를 작성하는 코드 자체가 컴파일러에 의해 타입 체크가 되므로, 쿼리에 문제가 있으면 컴파일 시점에 바로 알 수 있다.
JPAQueryFactory queryFactory = new JPAQueryFactory(entityManager);
QPost post = QPost.post;
List<Post> result = queryFactory.selectFrom(post)
.where(post.title.contains("yourTitle"))
.fetch();
장점: 코드가 간결하고 가독성이 좋다. 또한, 컴파일 시점에 쿼리 오류를 잡을 수 있어 안정성이 좋다.
단점: 추가적인 라이브러리를 필요로 하다. 따라서 프로젝트 설정이 복잡하며, 학습 비용이 크다.
@Query
애노테이션을 이용하여 JPQL을 작성할 수 있다.
public interface PostRepository extends JpaRepository<Post, Long> {
@Query("SELECT p FROM Post p WHERE p.title = :title")
List<Post> findByTitle(@Param("title") String title);
}
:title
은 파라미터 바인딩을 나타낸다. @Param 애노테이션을 이용하여 메서드 파라미터와 바인딩할 수 있다.
장점
쿼리의 가독성 향상: @Query
를 사용하면 메서드 위에 직접 쿼리를 작성할 수 있으므로, 메서드가 수행하는 쿼리를 쉽게 파악할 수 있다.
복잡한 쿼리 작성 가능: @Query
를 이용하면 JPQL 뿐만 아니라 네이티브 SQL도 작성할 수 있다. 따라서 복잡한 쿼리나 데이터베이스에 특화된 쿼리를 작성하는데 유용하다.
동적 쿼리 작성 용이: @Query
를 사용하면 쿼리에 동적으로 파라미터를 바인딩할 수 있다. 이는 동적 쿼리를 작성하는데 유용하다.
단점
쿼리 오류 발견이 늦음: @Query
에 작성한 쿼리의 오류는 애플리케이션을 실행한 후에야 발견할 수 있다. 이는 개발 과정에서 시간을 낭비할 수 있다.
코드 중복 가능성: 비슷한 쿼리가 여러 메서드에서 사용될 경우, @Query
를 이용하면 같은 쿼리를 여러 번 작성해야 할 수 있다. 이는 코드의 중복을 초래할 수 있다.
QueryDSL 등 다른 쿼리 작성 방식과 비교했을 때 타입 안정성이 떨어짐: @Query
는 문자열로 쿼리를 작성하므로, 컴파일 시점에 쿼리 오류를 잡아내지 못하고, 실행 시점에 오류가 발견된다. 이에 반해 QueryDSL은 타입 세이프한 쿼리를 작성할 수 있어 안정성이 더 높다.