[SpringBoot] 2024 게시판 만들기 Q&A ⑥ - JPQL, 네이티브 쿼리

SihoonCho·2024년 11월 25일
0
post-thumbnail

※ 읽기에 앞서


본 시리즈는 작성자의 이해와 경험을 바탕으로 실습 위주의 설명을 제공하고자 작성되었습니다.
실습 중심의 이해를 목표로 작성되었기 때문에, 다소 과장되거나 생략된 부분이 있을 수 있습니다.
따라서, 이론적으로 미흡한 부분이 있을 수 있는 점에 유의하시고 양해 부탁드립니다.

또한, Spring Boot 기반의 Backend 개발에 중점을 두고 설명하고 있으므로,
Frontend와 관련된 내용은 별도의 참고자료를 검색/활용하실 것을 권장드립니다.


📌 JPQL


JPQL(Java Persistence Query Language)JPA에서 객체(Entity)를 기반으로 데이터를 조회하거나 조작하기 위해 사용하는 쿼리 언어입니다.

SQL과 유사하지만, 테이블 대신 엔티티속성을 대상으로 작업합니다.

Raw JPA vs Spring Data JPA


Raw JPA에서는 EntityManager, Spring Data JPA에서는 @Query@Param을 활용해 JPQL을 작성하고 실행할 수 있습니다.

  • Raw JPAEntityManagerJPQL을 직접 작성하여 데이터베이스와 상호작용합
  • Spring Data JPA에서는 @Query를 사용해 명시적으로 JPQL을 작성하거나,
    메서드 이름 기반 쿼리 메서드를 통해 간편하게 JPQL 생성

📖 JPQL 개념


특징

  • 데이터베이스 독립적
  • 테이블이 아닌 클래스와 속성을 기준으로 작성

작동 원리

  • JPQL 생성: JPA 메서드 호출 시 필요에 따라 내부적으로 JPQL 생성
  • 자동 변환: JPAJPQLNative SQL로 변환하여 데이터베이스에서 실행
  • 객체 지향 쿼리: 데이터베이스 테이블이 아닌 JPA엔티티를 대상으로 작성

JPQL vs Native SQL

  • JPQL: "SELECT e FROM Employee e WHERE e.name = :name"
  • Native SQL: "SELECT * FROM employee WHERE name = 'John'"

📖 JPQL 예제코드


✅ 패키지 구조

├── 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


Native QuerySQL을 직접 작성하여 실행하는 방법으로, 복잡한 쿼리나 데이터베이스 특정 기능을 사용할 때 유용합니다. JPA는 네이티브 쿼리를 실행해도 결과를 엔티티로 매핑합니다.

Spring Data JPA에서 Native Query를 사용할 수도 있습니다.
Spring Data JPA는 작성한 Native SQL을 그대로 데이터베이스에서 실행합니다.

복잡한 쿼리를 사용할 때 편리하지만, 데이터베이스 의존성이 높아질 수 있습니다.


📖 Native Query 개념


특징

  • 리스트로 반환되는 결과나 Pageable의 정렬이 자동으로 적용되지 않습니다.
    • Native QueryORDER BY를 명시적으로 추가
    • Service Layer에서 데이터를 수동으로 정렬 (Java Stream 등 활용)
    • 실무에서는 데이터베이스에서 정렬을 처리하는 것이 더 성능이 좋고 효율적

작동 원리

  • 직접 SQL 작성: JPQL 대신 SQL을 직접 작성해 데이터베이스에 명령 전달
  • 데이터베이스 종속적: 특정 DB의 문법을 사용해야 하므로 이식성이 떨어질 수 있음
  • JPA의 매핑 기능 활용: 결과를 엔티티에 매핑해 객체 지향적 접근 가능

사용 목적

  • 복잡한 SQL을 처리해야 하는 경우
  • 데이터베이스 고유 기능이나 최적화가 필요한 경우

📖 Native Query 예제코드


✅ 패키지 구조

├── 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 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);
}

📌 JPA vs JPQL vs Native Query


📖 JPA

@Repository
public interface PostRepository extends JpaRepository<Post, Long> {
    List<Post> findByTitle(String title);
}

📖 JPQL

@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);
}

📖 Native Query

@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와 관련된 내용은 별도의 참고자료를 검색/활용하실 것을 권장드립니다.
profile
개발을 즐길 줄 아는 백엔드 개발자

0개의 댓글