https://sundaland.tistory.com/413
[ ▶ Query Lookup Strategies ]
JPA 모듈은 두 가지 방식으로 쿼리를 정의할 수 있다. 첫 번째는 쿼리를 문자열로 수동으로 정의하는 방식이고, 두 번째는 메서드 이름을 기반으로 파생된 쿼리를 사용하는 방식이다.
[ ▷ 파생된 쿼리(Derived Queries) ]
Spring Data JPA에서는 메서드 이름을 기반으로 자동으로 쿼리를 생성하는 기능을 제공한다. 예를 들어, findByFirstNameStartingWith 같은 메서드 이름을 정의하면 JPA는 자동으로 LIKE 연산자를 사용해 "FirstName"이 특정 문자열로 시작하는 레코드를 검색하는 쿼리를 생성한다. 이런 파생된 쿼리에서 사용할 수 있는 여러 가지 술어(predicate)가 존재한다.
- IsStartingWith, StartingWith, StartsWith: 메서드 이름의 마지막에 붙여서 해당 필드가 특정 문자열로 시작하는지를 확인하는 쿼리를 생성.
- IsEndingWith, EndingWith, EndsWith: 필드가 특정 문자열로 끝나는지를 확인하는 쿼리 생성.
- IsNotContaining, NotContaining, NotContains: 필드가 특정 문자열을 포함하지 않는지를 확인하는 쿼리 생성.
- IsContaining, Containing, Contains: 필드가 특정 문자열을 포함하는지를 확인하는 쿼리 생성.
이러한 파생된 쿼리에서 사용하는 아규먼트들은 쿼리의 와일드카드 문자를 포함하고 있을 경우 자동으로 이스케이프 처리된다. 즉, LIKE 구문에서 사용되는 _%와 같은 와일드카드 문자가 포함된 아규먼트는 해당 문자가 실제로 문자열의 일부로 취급되도록 이스케이프된다. 예를 들어, 검색 값에 %가 들어가 있다면, 이를 일반 문자로 인식하도록 처리하는 것이다. 이때 이스케이프 문자는 기본적으로 \이지만, @EnableJpaRepositories의 escapeCharacter 속성을 설정하여 다른 이스케이프 문자를 지정할 수 있다.
[ ▷ 선언된 쿼리(Declared Queries) ]
메서드 이름을 기반으로 쿼리를 파생하는 것은 매우 편리한 방식이지만, 특정한 경우에는 메서드 이름이 너무 길어지거나, 사용하고자 하는 키워드를 지원하지 않는 문제가 발생할 수 있다. 이런 경우에는 다음과 같은 두 가지 대안이 있다.
- JPA Named Queries: JPA의 네이밍 규칙을 따르는 Named Queries를 사용하는 방법입니다. Named Queries는 엔티티 클래스에 쿼리를 미리 정의해두고 메서드에서 이를 호출하는 방식이다. 자세한 내용은 Using JPA Named Queries 부분에서 다룰 수 있다.
- @Query 애너테이션: 메서드에 직접 @Query 애너테이션을 붙여서 SQL 또는 JPQL 쿼리를 직접 정의할 수 있다. 이 방식은 복잡한 쿼리나 메서드 이름으로는 표현하기 어려운 쿼리를 사용할 때 유용합니다. 예를 들어, @Query("SELECT p FROM Post p WHERE p.title LIKE %:title%")와 같은 방식으로 직접 쿼리를 작성할 수 있다. 이는 메서드 이름 기반의 자동 생성보다 더 세밀한 제어가 가능하다.
이렇게 JPA에서는 파생된 쿼리와 선언된 쿼리 두 가지 방식을 통해 유연하게 데이터베이스 쿼리를 처리할 수 있다.
▼ User 테이블
CREATE TABLE User (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
first_name VARCHAR(255),
last_name VARCHAR(255),
email VARCHAR(255),
status VARCHAR(50)
);
▼ User 엔티티 클래
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
private String email;
private String status;
// 기본 생성자와 Getter/Setter 생략
}
▼ Repository 인터페이스 및 파생된 쿼리 예시
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface UserRepository extends JpaRepository<User, Long> {
// 파생된 쿼리 메서드
List<User> findByFirstNameStartingWith(String prefix);
List<User> findByLastNameContaining(String substring);
List<User> findByEmailNotContaining(String substring);
}
△ Hibernate가 생성하는 SQL
- findByFirstNameStartingWith: 주어진 접두사로 시작하는 firstName을 가진 유저를 찾는다.
SELECT u.id, u.first_name, u.last_name, u.email, u.status
FROM User u
WHERE u.first_name LIKE ? || '%';
- 여기서 ? || '%'는 쿼리 아규먼트 prefix로 시작하는 문자열을 의미한다.
- %는 SQL에서 와일드카드로 사용됩니다. 문자열 검색을 위한 LIKE 조건에서 사용되며, 이는 "아무 문자나 0개 이상"을 의미한다.
- 예를 들어, SQL 쿼리에서 LIKE 'prefix%'는 "prefix로 시작하고, 그 뒤에 어떤 문자열이 와도 상관없다"라는 의미이다. 즉, prefix 로 시작하는 모든 문자열을 찾습니다.
- 따라서 ? || '%'에서 %는 와일드카드로서 "어떤 문자열이든 뒤따를 수 있다"는 의미를 나타내고, ?는 메서드에서 전달된 아규먼트가 들어가는 자리이다. prefix || '%'는 prefix로 시작하는 문자열을 찾기 위해 사용되며, 여기서 prefix는 메서드 호출 시 전달된 값이다.
- 예를 들어, findByFirstNameStartingWith("John")이라는 메서드가 실행되면, 이 쿼리는 LIKE 'John%'로 변환되며, "John"으로 시작하는 모든 first_name 값을 가진 유저를 검색한다.
- findByLastNameContaining: lastName에 특정 문자열을 포함하는 유저를 찾다.
SELECT u.id, u.first_name, u.last_name, u.email, u.status
FROM User u
WHERE u.last_name LIKE '%' || ? || '%';
- '%' || ? || '%'는 SQL의 LIKE 절에서 사용되는 패턴으로, 특정 문자열을 부분적으로 포함하는 레코드를 찾기 위한 조건이다.
- %는 SQL에서 와일드카드로 사용되며, "임의의 문자 0개 이상"을 의미한다.
- ?는 Spring Data JPA에서 메서드에 전달되는 아규먼트 값을 바인딩할 자리이다.
- 따라서 '%' || ? || '%'의 의미는: 앞에 %, 뒤에 %가 붙어 있기 때문에, 어떤 문자열이든 그 중간에 ? 값이 포함된 문자열을 찾는다.
△ 예시
List<User> findByLastNameContaining(String substring);
이 메서드를 호출할 때 substring으로 "Smith"라는 값을 전달하면, 이 쿼리는 Hibernate에 의해 다음과 같은 SQL로 변환된다.
SELECT u.id, u.first_name, u.last_name, u.email, u.status
FROM User u
WHERE u.last_name LIKE '%' || 'Smith' || '%'
실제로는 다음과 같은 SQL로 실행된다.
SELECT u.id, u.first_name, u.last_name, u.email, u.status
FROM User u
WHERE u.last_name LIKE '%Smith%'
이 쿼리는 last_name이 "Smith"라는 문자열을 포함하는 모든 레코드를 반환한다. '%' || ? || '%'는 메서드 아규먼트로 주어진 문자열을 포함하는 모든 값을 찾기 위한 패턴이다.
△ 요약
- %' || ? || '%'는 "임의의 위치에 주어진 문자열을 포함하는" 레코드를 찾는 조건이다.
- 앞과 뒤에 %가 있으므로, 해당 문자열이 문자열의 처음, 중간, 끝 어디에 있든 상관없이 검색된다.
- findByEmailNotContaining: email에 특정 문자열을 포함하지 않는 유저를 찾다.
SELECT u.id, u.first_name, u.last_name, u.email, u.status
FROM User u
WHERE u.email NOT LIKE '%' || ? || '%';
이처럼 파생된 쿼리는 메서드 이름을 기반으로 적절한 SQL을 자동으로 생성하고 실행한다. 이를 통해 개발자는 SQL을 직접 작성할 필요 없이 간단하게 데이터를 조회할 수 있다.
[ ▷ 선언된 쿼리(Declared Queries) ]
때로는 메서드 이름으로 쿼리를 자동 생성하기 어려운 상황이 발생하거나, 복잡한 쿼리를 명시적으로 작성해야 할 경우가 있다. 이때는 @Query 애너테이션이나 Named Query를 사용하여 직접 쿼리를 정의할 수 있다.
▼ @Query 애너테이션 사용 예시
import org.springframework.data.jpa.repository.Query;
public interface UserRepository extends JpaRepository<User, Long> {
@Query("SELECT u FROM User u WHERE u.firstName = ?1 AND u.lastName = ?2")
List<User> findByFullName(String firstName, String lastName);
}
△ Hibernate가 생성하는 SQL
- findByFullName: 주어진 firstName과 lastName이 모두 일치하는 유저를 찾는다.
SELECT u.id, u.first_name, u.last_name, u.email, u.status
FROM User u
WHERE u.first_name = ? AND u.last_name = ?;
이 방식에서는 JPQL을 직접 정의할 수 있으며, Hibernate가 이를 SQL로 변환하여 실행한다.
[ ▷ Named Query 사용 예시 ]
import jakarta.persistence.NamedQuery;
@Entity
@NamedQuery(name = "User.findByStatus", query = "SELECT u FROM User u WHERE u.status = ?1")
public class User {
// 엔티티 필드 및 메서드 생략
}
// 리포지토리에서 네임드 쿼리 사용
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByStatus(String status);
}
△ Hibernate가 생성하는 SQL
- findByStatus: 주어진 status에 해당하는 유저를 찾는다.
SELECT u.id, u.first_name, u.last_name, u.email, u.status
FROM User u
WHERE u.status = ?;
Named Query는 미리 정의된 JPQL 쿼리를 통해 SQL을 실행하는 방식으로, 복잡한 쿼리나 재사용이 필요한 쿼리에서 유용하게 사용할 수 있다.
Spring Data JPA는 쿼리 정의의 유연성을 제공합니다. 파생된 쿼리는 메서드 이름을 기반으로 자동으로 SQL을 생성하고, 선언된 쿼리는 개발자가 직접 정의한 JPQL을 기반으로 SQL을 생성한다. 이 과정은 Hibernate가 담당하며, 복잡한 SQL 쿼리를 자동으로 처리하여 개발자가 비즈니스 로직에 더 집중할 수 있도록 돕는다.
- 파생된 쿼리는 메서드 이름을 기반으로 간단한 데이터 조회를 빠르게 구현할 수 있다.
- 선언된 쿼리는 복잡한 비즈니스 로직에 맞춘 세밀한 데이터 조회가 필요할 때 유용하다.
이를 통해 Spring Data JPA와 Hibernate는 데이터베이스와 상호작용하는 과정을 자동화하고, 개발자의 생산성을 높이는 데 크게 기여한다.
[ ▷ 참고 ]
||는 SQL에서 문자열을 연결(concatenate)하는 연산자이다. 이 연산자는 두 개의 문자열을 하나로 결합하는 데 사용된다.
△ ||의 역할
- || 연산자는 왼쪽과 오른쪽에 있는 문자열을 연결하여 하나의 긴 문자열로 만든다.
- 예를 들어, 'Hello' || ' ' || 'World'는 'Hello World'라는 문자열을 생성한다.
△ 예시
SELECT u.id, u.first_name, u.last_name
FROM User u
WHERE u.last_name LIKE '%' || 'Smith' || '%'
실제로는 다음과 같이 실행된다.
WHERE u.last_name LIKE '%Smith%'
- 여기서 || 연산자는 '%', 'Smith' (전달된 값), 그리고 '%'를 하나의 문자열로 결합하여 '%Smith%' 패턴을 만들어냅니다.
△ 예시에서의 사용
SQL 조건에서 '%' || ? || '%'는 와일드카드 %와 메서드 아규먼트(?)를 연결하여, 검색 패턴을 생성하는 데 사용된다.
- 왼쪽에 % 와일드카드를 추가하고,
- ?는 메서드 아규먼트로 전달된 값(예: substring),
- 오른쪽에 % 와일드카드를 추가하여,
이들을 하나의 문자열로 결합한다. 즉, '%substring%'와 같은 결과를 만들어낸다.
△ 요약
- ||는 SQL에서 문자열을 연결하는 연산자이다.
- '%' || ? || '%'는 와일드카드 %와 전달된 아규먼트 ?를 연결하여 해당 아규먼트가 포함된 문자열을 찾는 패턴을 만든다.
'Spring Boot > Spring Data JPA' 카테고리의 다른 글
Reserved Method Names (0) | 2024.10.22 |
---|---|
Query Creation (0) | 2024.10.22 |
Defining Query Methods (0) | 2024.10.22 |
Persisting Entities (0) | 2024.10.22 |
Configuration (0) | 2024.10.22 |