본 시리즈는 작성자의 이해와 경험을 바탕으로 실습 위주의 설명을 제공하고자 작성되었습니다.
실습 중심의 이해를 목표로 작성되었기 때문에, 다소 과장되거나 생략된 부분이 있을 수 있습니다.
따라서, 이론적으로 미흡한 부분이 있을 수 있는 점에 유의하시고 양해 부탁드립니다.
또한, Spring Boot 기반의 Backend 개발에 중점을 두고 설명하고 있으므로,
Frontend와 관련된 내용은 별도의 참고자료를 검색/활용하실 것을 권장드립니다.
JPQL(Java Persistence Query Language)
은JPA
에서 객체(Entity)를 기반으로 데이터를 조회하거나 조작하기 위해 사용하는쿼리 언어
입니다.
SQL
과 유사하지만, 테이블 대신엔티티
와속성
을 대상으로 작업합니다.
Raw JPA vs Spring Data JPA
Raw JPA
에서는EntityManager
,Spring Data JPA
에서는@Query
와@Param
을 활용해JPQL
을 작성하고 실행할 수 있습니다.
Raw JPA
의EntityManager
는JPQL
을 직접 작성하여 데이터베이스와 상호작용합Spring Data JPA
에서는@Query
를 사용해 명시적으로JPQL
을 작성하거나,
메서드 이름 기반 쿼리 메서드를 통해 간편하게JPQL
생성
특징
- 데이터베이스 독립적
- 테이블이 아닌 클래스와 속성을 기준으로 작성
작동 원리
- JPQL 생성:
JPA
메서드 호출 시 필요에 따라 내부적으로JPQL
생성- 자동 변환:
JPA
가JPQL
을Native SQL
로 변환하여 데이터베이스에서 실행- 객체 지향 쿼리: 데이터베이스 테이블이 아닌
JPA
의엔티티
를 대상으로 작성
JPQL vs Native SQL
JPQL
:"SELECT e FROM Employee e WHERE e.name = :name"
Native SQL
:"SELECT * FROM employee WHERE name = 'John'"
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com.companyname.projectname/
│ │ │ ├── post/
│ │ │ │ ├── entity
│ │ │ │ │ └── Post.java
│ │ │ │ ├── repository
│ │ │ │ │ └── PostRepository.java
│ │ │ │ ├── service
│ │ │ │ │ └── PostService.java
│ │ │ │ └── controller
│ │ │ │ └── PostController.java
│ │ │ └── ProjectNameApplication.java
│ │ └── resources
│ └── test
PostRepository
package com.companyname.projectname.post.repository;
import com.companyname.projectname.post.model.Post;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.time.LocalDateTime;
import java.util.List;
@Repository
public interface PostRepository extends JpaRepository<Post, Long> {
// Spring Data JPA JPQL (Java Persistence Query Language) 방식
@Query("SELECT p FROM Post p WHERE p.title = :title")
List<Post> findByTitle(@Param("title") String title);
}
PostService
package com.companyname.projectname.post.service;
import com.companyname.projectname.post.entity.Post;
import com.companyname.projectname.post.repository.PostRepository;
import jakarta.persistence.EntityManager;
import jakarta.persistence.TypedQuery;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class PostService {
private final EntityManager entityManager;
private final PostRepository postRepository;
// Spring Data JPA JPQL 방식
public List<Post> getPostsByTitle(String title) {
return postRepository.findByTitle(title);
}
// EntityManager JPA JPQL 방식 1
public List<Post> getPostsByTitleJPQL(String title) {
TypedQuery<Post> query = entityManager.createQuery(
"SELECT p FROM Post p WHERE p.title = :title", Post.class);
query.setParameter("title", title);
return query.getResultList();
}
// EntityManager JPA JPQL 방식 2
public List<Post> getPostsByTitleJPQL(String title) {
return entityManager.createQuery(
"SELECT p FROM Post p WHERE p.title = :title", Post.class)
.setParameter("title", title)
.getResultList();
}
}
다양한 응용 예시
package com.companyname.projectname.post.repository;
import com.companyname.projectname.post.model.Post;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.time.LocalDateTime;
import java.util.List;
@Repository
public interface PostRepository extends JpaRepository<Post, Long> {
// Spring Data JPA JPQL (Java Persistence Query Language) 기반 메서드
// 기본 메서드
@Query("SELECT p FROM Post p WHERE p.title = :title")
List<Post> findByTitle(@Param("title") String title);
@Query("SELECT p FROM Post p WHERE p.content = :content")
List<Post> findByContent(@Param("content") String content);
@Query("SELECT p FROM Post p WHERE p.title LIKE CONCAT('%', :title, '%')")
List<Post> findByTitleContaining(@Param("title") String title);
@Query("SELECT p FROM Post p WHERE p.content LIKE CONCAT('%', :content, '%')")
List<Post> findByContentContaining(@Param("content") String content);
@Query("SELECT p FROM Post p WHERE LOWER(p.title) LIKE LOWER(CONCAT('%', :title, '%'))")
List<Post> findByTitleContainingIgnoreCase(@Param("title") String title);
@Query("SELECT p FROM Post p WHERE LOWER(p.content) LIKE LOWER(CONCAT('%', :content, '%'))")
List<Post> findByContentContainingIgnoreCase(@Param("content") String content);
// 복합 메서드
@Query("SELECT p FROM Post p WHERE p.title = :title AND p.content = :content")
List<Post> findByTitleAndContent(@Param("title") String title, @Param("content") String content);
@Query("SELECT p FROM Post p WHERE p.title LIKE CONCAT('%', :title, '%') OR p.content LIKE CONCAT('%', :content, '%')")
List<Post> findByTitleContainingOrContentContaining(@Param("title") String title, @Param("content") String content);
@Query("SELECT p FROM Post p WHERE p.title LIKE CONCAT('%', :title, '%') AND p.content LIKE CONCAT('%', :content, '%')")
List<Post> findByTitleContainingAndContentContaining(@Param("title") String title, @Param("content") String content);
@Query("SELECT p FROM Post p WHERE LOWER(p.title) LIKE LOWER(CONCAT('%', :title, '%')) OR LOWER(p.content) LIKE LOWER(CONCAT('%', :content, '%'))")
List<Post> findByTitleContainingIgnoreCaseOrContentContainingIgnoreCase(@Param("title") String title, @Param("content") String content);
@Query("SELECT p FROM Post p WHERE LOWER(p.title) LIKE LOWER(CONCAT('%', :title, '%')) AND LOWER(p.content) LIKE LOWER(CONCAT('%', :content, '%'))")
List<Post> findByTitleContainingIgnoreCaseAndContentContainingIgnoreCase(@Param("title") String title, @Param("content") String content);
// 날짜 기반 메서드 (생성 일자 기준)
@Query("SELECT p FROM Post p WHERE p.createdDate > :date")
List<Post> findByCreatedDateAfter(@Param("date") LocalDateTime date);
@Query("SELECT p FROM Post p WHERE p.createdDate < :date")
List<Post> findByCreatedDateBefore(@Param("date") LocalDateTime date);
@Query("SELECT p FROM Post p WHERE p.createdDate BETWEEN :sDate AND :eDate")
List<Post> findByCreatedDateBetween(@Param("sDate") LocalDateTime sDate, @Param("eDate") LocalDateTime eDate);
// 날짜 기반 메서드 (수정 일자 기준)
@Query("SELECT p FROM Post p WHERE p.updatedDate > :date")
List<Post> findByUpdatedDateAfter(@Param("date") LocalDateTime date);
@Query("SELECT p FROM Post p WHERE p.updatedDate < :date")
List<Post> findByUpdatedDateBefore(@Param("date") LocalDateTime date);
@Query("SELECT p FROM Post p WHERE p.updatedDate BETWEEN :sDate AND :eDate")
List<Post> findByUpdatedDateBetween(@Param("sDate") LocalDateTime sDate, @Param("eDate") LocalDateTime eDate);
}
Native Query
는SQL
을 직접 작성하여 실행하는 방법으로, 복잡한 쿼리나 데이터베이스 특정 기능을 사용할 때 유용합니다.JPA
는 네이티브 쿼리를 실행해도 결과를 엔티티로 매핑합니다.
Spring Data JPA
에서Native Query
를 사용할 수도 있습니다.
Spring Data JPA
는 작성한Native SQL
을 그대로 데이터베이스에서 실행합니다.복잡한 쿼리를 사용할 때 편리하지만, 데이터베이스 의존성이 높아질 수 있습니다.
특징
- 리스트로 반환되는 결과나
Pageable
의 정렬이 자동으로 적용되지 않습니다.
Native Query
에ORDER BY
를 명시적으로 추가Service Layer
에서 데이터를 수동으로 정렬 (Java Stream 등 활용)- 실무에서는 데이터베이스에서 정렬을 처리하는 것이 더 성능이 좋고 효율적
작동 원리
- 직접 SQL 작성:
JPQL
대신SQL
을 직접 작성해 데이터베이스에 명령 전달- 데이터베이스 종속적: 특정 DB의 문법을 사용해야 하므로 이식성이 떨어질 수 있음
- JPA의 매핑 기능 활용: 결과를 엔티티에 매핑해 객체 지향적 접근 가능
사용 목적
- 복잡한
SQL
을 처리해야 하는 경우- 데이터베이스 고유 기능이나 최적화가 필요한 경우
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com.companyname.projectname/
│ │ │ ├── post/
│ │ │ │ ├── entity
│ │ │ │ │ └── Post.java
│ │ │ │ ├── repository
│ │ │ │ │ └── PostRepository.java
│ │ │ │ ├── service
│ │ │ │ │ └── PostService.java
│ │ │ │ └── controller
│ │ │ │ └── PostController.java
│ │ │ └── ProjectNameApplication.java
│ │ └── resources
│ └── test
package com.companyname.projectname.post.repository;
import com.companyname.projectname.post.model.Post;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.time.LocalDateTime;
import java.util.List;
@Repository
public interface PostRepository extends JpaRepository<Post, Long> {
// Spring Data JPA Native Query 방식
@Query(value = "SELECT * FROM post WHERE title = :title ORDER BY id ASC", nativeQuery = true)
List<Post> findByTitle(@Param("title") String title);
}
PostService
package com.companyname.projectname.post.service;
import com.companyname.projectname.post.entity.Post;
import com.companyname.projectname.post.repository.PostRepository;
import jakarta.persistence.EntityManager;
import jakarta.persistence.TypedQuery;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class PostService {
private final EntityManager entityManager;
private final PostRepository postRepository;
// Spring Data JPA Native Query 방식
public List<Post> getPostsByTitle(String title) {
return postRepository.findByTitle(title);
}
// EntityManager JPA Native Query 방식 1
public List<Post> getPostsByTitleJPQL(String title) {
TypedQuery<Post> query = entityManager.createNativeQuery(
"SELECT * FROM post WHERE title = :title", Post.class);
query.setParameter("title", title);
return query.getResultList();
}
// EntityManager JPA Native Query 방식 2
public List<Post> getPostsByTitleJPQL(String title) {
return entityManager.createNativeQuery(
"SELECT * FROM post WHERE title = :title", Post.class)
.setParameter("title", title)
.getResultList();
}
}
다양한 응용 예시
package com.companyname.projectname.post.repository;
import com.companyname.projectname.post.model.Post;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.time.LocalDateTime;
import java.util.List;
@Repository
public interface PostRepository extends JpaRepository<Post, Long> {
// Spring Data JPA Native Query 기반 메서드
// 기본 메서드
@Query(value = "SELECT * FROM post WHERE title = :title ORDER BY id ASC", nativeQuery = true)
List<Post> findByTitle(@Param("title") String title);
@Query(value = "SELECT * FROM post WHERE content = :content ORDER BY id ASC", nativeQuery = true)
List<Post> findByContent(@Param("content") String content);
@Query(value = "SELECT * FROM post WHERE title LIKE CONCAT('%', :title, '%') ORDER BY id ASC", nativeQuery = true)
List<Post> findByTitleContaining(@Param("title") String title);
@Query(value = "SELECT * FROM post WHERE content LIKE CONCAT('%', :content, '%') ORDER BY id ASC", nativeQuery = true)
List<Post> findByContentContaining(@Param("content") String content);
@Query(value = "SELECT * FROM post WHERE LOWER(title) LIKE LOWER(CONCAT('%', :title, '%')) ORDER BY id ASC", nativeQuery = true)
List<Post> findByTitleContainingIgnoreCase(@Param("title") String title);
@Query(value = "SELECT * FROM post WHERE LOWER(content) LIKE LOWER(CONCAT('%', :content, '%')) ORDER BY id ASC", nativeQuery = true)
List<Post> findByContentContainingIgnoreCase(@Param("content") String content);
// 복합 메서드
@Query(value = "SELECT * FROM post WHERE title = :title AND content = :content ORDER BY id ASC", nativeQuery = true)
List<Post> findByTitleAndContent(@Param("title") String title, @Param("content") String content);
@Query(value = "SELECT * FROM post WHERE title LIKE CONCAT('%', :title, '%') OR content LIKE CONCAT('%', :content, '%') ORDER BY id ASC", nativeQuery = true)
List<Post> findByTitleContainingOrContentContaining(@Param("title") String title, @Param("content") String content);
@Query(value = "SELECT * FROM post WHERE title LIKE CONCAT('%', :title, '%') AND content LIKE CONCAT('%', :content, '%') ORDER BY id ASC", nativeQuery = true)
List<Post> findByTitleContainingAndContentContaining(@Param("title") String title, @Param("content") String content);
@Query(value = "SELECT * FROM post WHERE LOWER(title) LIKE LOWER(CONCAT('%', :title, '%')) OR LOWER(content) LIKE LOWER(CONCAT('%', :content, '%')) ORDER BY id ASC", nativeQuery = true)
List<Post> findByTitleContainingIgnoreCaseOrContentContainingIgnoreCase(@Param("title") String title, @Param("content") String content);
@Query(value = "SELECT * FROM post WHERE LOWER(title) LIKE LOWER(CONCAT('%', :title, '%')) AND LOWER(content) LIKE LOWER(CONCAT('%', :content, '%')) ORDER BY id ASC", nativeQuery = true)
List<Post> findByTitleContainingIgnoreCaseAndContentContainingIgnoreCase(@Param("title") String title, @Param("content") String content);
// 날짜 기반 메서드 (생성 일자 기준)
@Query(value = "SELECT * FROM post WHERE created_date > :date ORDER BY id ASC", nativeQuery = true)
List<Post> findByCreatedDateAfter(@Param("date") LocalDateTime date);
@Query(value = "SELECT * FROM post WHERE created_date < :date ORDER BY id ASC", nativeQuery = true)
List<Post> findByCreatedDateBefore(@Param("date") LocalDateTime date);
@Query(value = "SELECT * FROM post WHERE created_date BETWEEN :sDate AND :eDate ORDER BY id ASC", nativeQuery = true)
List<Post> findByCreatedDateBetween(@Param("sDate") LocalDateTime sDate, @Param("eDate") LocalDateTime eDate);
// 날짜 기반 메서드 (수정 일자 기준)
@Query(value = "SELECT * FROM post WHERE updated_date > :date ORDER BY id ASC", nativeQuery = true)
List<Post> findByUpdatedDateAfter(@Param("date") LocalDateTime date);
@Query(value = "SELECT * FROM post WHERE updated_date < :date ORDER BY id ASC", nativeQuery = true)
List<Post> findByUpdatedDateBefore(@Param("date") LocalDateTime date);
@Query(value = "SELECT * FROM post WHERE updated_date BETWEEN :sDate AND :eDate ORDER BY id ASC", nativeQuery = true)
List<Post> findByUpdatedDateBetween(@Param("sDate") LocalDateTime sDate, @Param("eDate") LocalDateTime eDate);
}
@Repository
public interface PostRepository extends JpaRepository<Post, Long> {
List<Post> findByTitle(String title);
}
@Repository
public interface PostRepository extends JpaRepository<Post, Long> {
@Query("SELECT p FROM Post p WHERE p.title = :title")
List<Post> findByTitle(@Param("title") String title);
}
@Repository
public interface PostRepository extends JpaRepository<Post, Long> {
@Query(value = "SELECT * FROM post WHERE title = :title ORDER BY id ASC", nativeQuery = true)
List<Post> findByTitle(@Param("title") String title);
}
차이점 정리
JPQL
:"SELECT p FROM Post p WHERE p.title = :title"
Native SQL
:"SELECT * FROM post WHERE title = :title"
본 시리즈는 작성자의 이해와 경험을 바탕으로 실습 위주의 설명을 제공하고자 작성되었습니다.
실습 중심의 이해를 목표로 작성되었기 때문에, 다소 과장되거나 생략된 부분이 있을 수 있습니다.
따라서, 이론적으로 미흡한 부분이 있을 수 있는 점에 유의하시고 양해 부탁드립니다.
또한, Spring Boot 기반의 Backend 개발에 중점을 두고 설명하고 있으므로,
Frontend와 관련된 내용은 별도의 참고자료를 검색/활용하실 것을 권장드립니다.