🙋 JPA에서 제공하는 여러가지 쿼리 생성 방법을 알아보자.
1. 쿼리 메소드(Query Methods) :
- 메소드 이름을 통해 JPQL 쿼리를 생성하는 방법
- Spring Data JPA에서 자동으로 메소드 이름을 분석하여 JPQL을 생성
메서드 이름을 특정한 규칙에 맞게 작성하면, Spring Data JPA는 해당 메서드를 실행할 때 자동으로 쿼리를 생성하여 데이터베이스에 전달한다.
public interface PostRepository extends JpaRepository<Post, Long> {
// 제목에 특정 키워드가 포함된 게시글 검색
List<Post> findByTitleContaining(String keyword);
예를 들어, findByTitleContaining 메서드는 다음과 같은 역할을 수행한다.
- findBy : 메서드 이름의 시작 부분으로, "findBy" 다음에 오는 속성명으로 쿼리를 생성한다.
- Title : 엔터티 클래스의 속성 중 하나이다. 여기서는 title이라는 속성을 의미한다.
- Containing : 속성에 대한 조건을 나타내며, 해당 속성이 특정 키워드를 포함하는지를 확인한다.
따라서, findByTitleContaining은 "title" 속성에서 특정 키워드를 포함하는 게시글을 찾는 쿼리를 생성한다.
Spring Data JPA는 이 메서드를 실행할 때, 자동으로 쿼리를 생성하고 실행한다.
예를 들어, 메서드를 호출할 때 findByTitleContaining("Java")와 같이 호출하면, Spring Data JPA는 "title에 'Java'를 포함하는 게시글을 찾아라"는 쿼리를 생성하고 실행한다.
이렇게 메서드 이름을 통해 동적으로 쿼리를 생성할 수 있어서 편리하게 사용할 수 있다.
2. JPQL (Java Persistence Query Language) :
쿼리 메소드는 특정한 규칙을 따르며, 이를 통해 자주 사용되는 CRUD 기능을 편리하게 제공한다.
하지만 복잡한 쿼리를 표현하기에는 한계가 있을 수 있다. 이런 경우에는 @Query 어노테이션을 사용하여 직접 JPQL을 작성한다.
- 객체지향 쿼리 언어로, 엔티티 객체를 대상으로 쿼리를 작성할 수 있다.
- SQL과 유사하지만 테이블이 아닌 엔티티에 대해 쿼리를 작성한다.
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface PostRepository extends JpaRepository<Post, Long> {
@Query("SELECT p FROM Post p WHERE p.title LIKE %:keyword%")
List<Post> findByTitleContainingCustom(@Param("keyword") String keyword);
// 다른 사용자 정의 쿼리 메서드들...
}
- SELECT p : 엔티티 객체인 Post를 선택합니다.
- FROM Post p : Post 엔터티를 대상으로 쿼리를 실행합니다. (p : Post의 별칭)
- WHERE p.title LIKE %:keyword%: title 속성이 특정 키워드를 포함하는 경우를 선택합니다.
JPQL은 엔티티 객체를 기반으로 작성되기 때문에 테이블과 컬럼의 이름이 아닌 엔터티 클래스와 그 속성명을 사용합니다. 이를 통해 데이터베이스 종속성을 줄일 수 있습니다.
💡 만약 네이티브 SQL을 사용하고 싶다면.. nativeQuery 속성을 true로 설정
@Query(value = "SELECT * FROM post WHERE title LIKE %:keyword%", nativeQuery = true)
List<Post> findByTitleContainingNative(@Param("keyword") String keyword);
이렇게 하면 직접 SQL을 작성하여 원하는 쿼리를 실행할 수 있다.
다만 주의할 점은 네이티브 SQL을 사용할 때는 데이터베이스 종속성이 생길 수 있으므로(보안 문제 발생 가능성도 있음),
가능한 JPQL을 사용하여 데이터베이스에 독립적인 쿼리를 작성하는 것이 좋다.
3. QueryDSL(Domain Specific Language) :
@Query 어노테이션은 명시적인 JPQL을 작성할 수 있게 해주지만, 쿼리가 복잡해지면 가독성이나 유지보수 측면에서 한계가 있을 수 있다. 이런 경우에 QueryDSL을 사용하면 좀 더 강력하고 유연한 쿼리를 작성할 수 있다.
QueryDSL은 JPQL을 자바 코드로 작성할 수 있도록 도와주는 라이브러리로, 코드 자체에서 쿼리를 구성할 수 있게 해준다. 이를 통해 컴파일 시점에서 타입 안정성을 확보하고, 복잡한 쿼리를 더 직관적으로 작성할 수 있다.
💡 QueryDSL 예시 코드를 보며 전체적인 흐름을 확인해보자.
1) 엔티티 클래스 (Post) :
엔티티 클래스는 데이터베이스의 테이블을 나타내는데 사용된다.
QueryDSL은 이 엔티티 클래스를 기반으로 Query 타입을 생성한다.
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Post {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
// Getter, Setter, 기타 메서드...
}
2) QueryDSL Query 타입 (QPost):
QPost 클래스는 QueryDSL을 사용하여 JPA 엔티티인 Post에 대한 타입 안전한(Query 타입) 표현을 정의한 클래스이다.
이 클래스를 사용하면 코드에서 엔티티의 속성에 대한 오타나 오류를 컴파일 시간에 확인할 수 있다.
import com.querydsl.core.types.dsl.NumberPath;
import com.querydsl.core.types.dsl.StringPath;
import com.querydsl.core.types.dsl.EntityPathBase;
import com.querydsl.core.types.dsl.Expressions;
public class QPost extends EntityPathBase<Post> {
public final StringPath title = createString("title");
public final NumberPath<Long> id = createNumber("id", Long.class);
public QPost(String variable) {
super(Post.class, variable);
}
}
- QPost extends EntityPathBase<Post> : EntityPathBase는 QueryDSL에서 사용하는 기본 클래스로, 엔티티에 대한 QueryDSL Query 타입을 생성하기 위한 메타 모델 클래스를 정의하는 데 사용된다.
- public final StringPath title = createString("title"); : 이 부분은 title 필드에 대한 QueryDSL Query 타입을 정의한다. StringPath는 문자열에 대한 QueryDSL 타입을 나타내며, createString("title")은 title이라는 필드에 대한 QueryDSL 표현식을 생성한다.
- public final NumberPath<Long> id = createNumber("id", Long.class); : 이 부분은 id 필드에 대한 QueryDSL Query 타입을 정의한다. NumberPath<Long>는 숫자(Long)에 대한 QueryDSL 타입을 나타내며, createNumber("id", Long.class)은 id라는 필드에 대한 QueryDSL 표현식을 생성한다.
- public QPost(String variable) { super(Post.class, variable); } : 생성자는 QPost 클래스의 인스턴스를 초기화한다. Post.class는 이 QueryDSL Query 타입이 어떤 엔티티를 대상으로 하는지를 나타내며, variable은 QueryDSL에서 사용될 변수명을 지정한다.
이 클래스를 사용하면 QueryDSL을 활용하여 동적으로 쿼리를 작성할 수 있다.
예를 들어, QPost.post.title.eq("제목")와 같은 형태로 사용하여 title 필드가 "제목"과 같은지를 나타내는 조건을 만들 수 있다. QueryDSL을 사용하면 타입 안정성(type safety)을 확보하면서 동적 쿼리를 생성할 수 있다.
3) Repository 인터페이스 (PostRepository):
PostRepository 인터페이스는 Spring Data JPA의 CrudRepository를 확장하고, QueryDSL을 사용하여 동적인 쿼리를 처리하기 위한 QuerydslPredicateExecutor를 구현한다. (Spring Data JPA의 기본 기능 정의)
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.data.repository.CrudRepository;
public interface PostRepository extends CrudRepository<Post, Long>, QuerydslPredicateExecutor<Post> {
// 다른 메서드들...
// QueryDSL을 사용한 동적인 쿼리
List<Post> findAll(Predicate predicate);
// Spring Data JPA가 제공하는 메서드 활용
List<Post> findByTitle(String title);
}
4) Custom Repository 인터페이스 (CustomPostRepository) :
QueryDSL을 사용한 동적 쿼리를 직접 구현하는 인터페이스이다.
사용자가 정의한 쿼리 메서드를 추가한 인터페이스.
import java.util.List;
public interface CustomPostRepository {
// QueryDSL을 사용한 동적인 쿼리
List<Post> findPostsByTitle(String keyword);
}
5) Custom Repository 구현 클래스 (PostRepositoryImpl):
CustomPostRepository 인터페이스에서 정의된 메서드를 구현하는 클래스.
여기에서 QueryDSL을 사용하여 동적인 쿼리를 작성하고 실행한다.
import com.querydsl.jpa.impl.JPAQueryFactory;
import javax.persistence.EntityManager;
public class PostRepositoryImpl implements CustomPostRepository {
private final JPAQueryFactory queryFactory;
private final QPost qPost = QPost.post;
public PostRepositoryImpl(EntityManager entityManager) {
this.queryFactory = new JPAQueryFactory(entityManager);
}
@Override
public List<Post> findPostsByTitle(String keyword) {
return queryFactory.selectFrom(qPost)
.where(qPost.title.like("%" + keyword + "%"))
.fetch();
}
}
✏️ 정리
이 외에 Named Query, Criteria API 방법도 존재한다.
여러 가지 방법을 적절히 조합하여 사용하는 것이 중요하며, 간단한 쿼리에는 메서드 이름 규칙을, 복잡한 쿼리에는 @Query 어노테이션을 또는 QueryDSL을 활용하는 것이 좋다.