
본 시리즈는 작성자의 이해와 경험을 바탕으로 실습 위주의 설명을 제공하고자 작성되었습니다.
실습 중심의 이해를 목표로 작성되었기 때문에, 다소 과장되거나 생략된 부분이 있을 수 있습니다.
따라서, 이론적으로 미흡한 부분이 있을 수 있는 점에 유의하시고 양해 부탁드립니다.
또한, 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
PostRepositorypackage 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);
}
PostServicepackage 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);
}
PostServicepackage 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와 관련된 내용은 별도의 참고자료를 검색/활용하실 것을 권장드립니다.