@Query를 활용한 JPQL 및 네이티브 쿼리
명시적인 JPQL 및 네이티브 SQL 쿼리를 작성하고 이름 지정 및 위치 기반 매개변수를 바인딩하며 프로젝션을 매핑합니다.
@Query를 활용한 JPQL 및 네이티브 쿼리은(는) CoddyKit의 무료 Spring Boot 4 Complete Guide 강의입니다. 이것은 4개 중 2번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 Spring Boot 4 Complete Guide 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. Spring Boot 4 Complete Guide 강의에는 총 4개의 강의가 포함되어 있습니다.
이 강의의 일부는 아직 번역되지 않았으며 영어로 표시됩니다.
Why @Query Exists
Spring Data JPA can derive queries from method names like findByLastName, but derived queries break down for anything non-trivial: joins across entities, aggregations, custom projections, or fine-tuned SQL.
The @Query annotation lets you attach an explicit query to a repository method. You write the query once, declaratively, and Spring binds the method's parameters and maps the result.
- JPQL — object-oriented query language that works against entities and fields.
- Native SQL — raw database SQL when you need vendor features or hand-tuned queries.
A Basic JPQL @Query
JPQL looks like SQL but operates on entity names and Java field names, not table and column names. Here User is the entity class and email is a Java field.
Notice the placeholder ?1 — this is a positional parameter bound to the first method argument.
public interface UserRepository extends JpaRepository<User, Long> {
@Query("SELECT u FROM User u WHERE u.email = ?1")
Optional<User> findByEmailAddress(String email);
}Named Parameters with @Param
Positional parameters (?1, ?2) work, but they break when you reorder arguments. Named parameters are clearer and safer: write :name in the query and bind it with @Param("name").
- The string in
@Parammust match the:placeholderexactly. - Order of method arguments no longer matters.
public interface UserRepository extends JpaRepository<User, Long> {
@Query("SELECT u FROM User u WHERE u.status = :status AND u.age >= :minAge")
List<User> findActiveAdults(@Param("status") String status,
@Param("minAge") int minAge);
}Named vs Positional — Which to Use
Both styles bind method arguments into the query, but they differ in maintainability.
- Positional (
?1) — concise for one or two params; fragile when you add or reorder arguments. - Named (
:status) — self-documenting and resilient to refactoring; preferred for queries with multiple parameters.
The team standard in Spring Boot 4 projects is to favor named parameters for readability. Reserve positional parameters for very short queries.
Native SQL Queries
When you need database-specific SQL — window functions, vendor extensions, or a hand-optimized statement — set nativeQuery = true. Now the query runs as raw SQL against table and column names, not entity fields.
The result is still mapped back to the User entity because the selected columns match the entity's table.
public interface UserRepository extends JpaRepository<User, Long> {
@Query(value = "SELECT * FROM users WHERE email = :email",
nativeQuery = true)
Optional<User> findByEmailNative(@Param("email") String email);
}JPQL vs Native — Picking the Right Tool
Default to JPQL; reach for native SQL only when JPQL can't express what you need.
- JPQL — portable across databases, refactor-safe (uses Java field names), integrates with the persistence context.
- Native — full SQL power and vendor features, but ties you to one database dialect and bypasses some JPA conveniences.
A common pitfall: in native queries LIKE wildcards and pagination must follow your database's SQL, not JPQL rules.
Interface-Based Projections
Often you don't need the whole entity — just a few columns. A projection returns a lightweight view instead of a full User.
Define an interface with getters; Spring matches each getter to a selected alias. This loads only the columns you ask for.
public interface UserSummary {
String getName();
String getEmail();
}
public interface UserRepository extends JpaRepository<User, Long> {
@Query("SELECT u.name AS name, u.email AS email FROM User u WHERE u.active = true")
List<UserSummary> findActiveSummaries();
}Aliases Matter for Projections
For an interface projection to bind, each selected expression must be aliased to match the getter name. The getter getEmail() maps to alias email.
SELECT u.email AS emailbinds togetEmail().- Omitting the alias on a computed column leaves the getter unmapped and returns
null.
This holds for both JPQL and native projection queries.
DTO Projections with Constructor Expressions
JPQL also supports constructor expressions: build a DTO directly in the query with the NEW keyword. You must use the fully-qualified class name and match a constructor's parameter order.
This gives you an immutable, typed result object instead of an interface proxy.
public record UserDto(String name, String email) {}
public interface UserRepository extends JpaRepository<User, Long> {
@Query("SELECT new com.example.app.UserDto(u.name, u.email) FROM User u WHERE u.active = true")
List<UserDto> findActiveDtos();
}Modifying Queries
@Query can also run UPDATE and DELETE statements. These require @Modifying so Spring executes them as updates rather than selects, and they typically run inside a @Transactional method.
The return value is the count of affected rows.
public interface UserRepository extends JpaRepository<User, Long> {
@Modifying
@Transactional
@Query("UPDATE User u SET u.status = :status WHERE u.lastLogin < :cutoff")
int deactivateStale(@Param("status") String status,
@Param("cutoff") LocalDate cutoff);
}A Standalone JPQL Mental Model
JPQL parameter binding mirrors how you'd substitute values yourself. The snippet below is plain Java that demonstrates the named-parameter substitution idea behind :status and :minAge — no database required.
In real code Spring does this binding safely via prepared statements; this just illustrates the concept.
import java.util.Map;
public class ParamBindingDemo {
static String bind(String query, Map<String, String> params) {
for (Map.Entry<String, String> e : params.entrySet()) {
query = query.replace(":" + e.getKey(), e.getValue());
}
return query;
}
public static void main(String[] args) {
String jpql = "SELECT u FROM User u WHERE u.status = :status AND u.age >= :minAge";
Map<String, String> params = Map.of("status", "'ACTIVE'", "minAge", "18");
System.out.println(bind(jpql, params));
}
}Quick Check
Test your understanding of @Query parameter binding and projections.
Recap
You now know how to write explicit queries with @Query:
- JPQL works on entity and field names; native SQL (
nativeQuery = true) works on tables and columns. - Bind values with positional (
?1) or, preferably, named (:name+@Param) parameters. - Interface projections need aliases matching getter names; constructor expressions (
SELECT new ...) build typed DTOs. - Use
@Modifying(with@Transactional) forUPDATE/DELETEqueries.
Default to JPQL for portability; drop to native SQL only when you truly need database-specific power.
자주 묻는 질문
“@Query를 활용한 JPQL 및 네이티브 쿼리” 강의는 무료인가요?
네 — “@Query를 활용한 JPQL 및 네이티브 쿼리” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Spring Boot 4 Complete Guide 강의 전체를 잠금 해제할 수 있습니다. Spring Boot 4 Complete Guide 강의에는 총 4개의 강의가 포함되어 있습니다.
“@Query를 활용한 JPQL 및 네이티브 쿼리”에서 뭘 배우나요?
명시적인 JPQL 및 네이티브 SQL 쿼리를 작성하고 이름 지정 및 위치 기반 매개변수를 바인딩하며 프로젝션을 매핑합니다. 브라우저에서 직접 실행하는 실습 코드로 Spring Boot 4 Complete Guide을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
Spring Boot 4 Complete Guide을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 Spring Boot 4 Complete Guide은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 2번째 강의입니다.
“@Query를 활용한 JPQL 및 네이티브 쿼리” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 Spring Boot 4 Complete Guide 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 Spring Boot 4 Complete Guide 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- 파생 쿼리 메서드와 키워드 해석
- @Query를 활용한 JPQL 및 네이티브 쿼리
- Specification과 기준 기반 동적 필터링
- 페이지 매김, 정렬 및 슬라이스 스트리밍