0Pricing
Spring Boot 4 Complete Guide · 课时

使用 @Query 编写 JPQL 与原生查询

编写明确的 JPQL 和原生 SQL 查询,绑定命名参数和位置参数,并映射投影。

使用 @Query 编写 JPQL 与原生查询 是 CoddyKit 上的免费 Spring Boot 4 Complete Guide 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 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 @Param must match the :placeholder exactly.
  • 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 email binds to getEmail().
  • 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) for UPDATE/DELETE queries.

Default to JPQL for portability; drop to native SQL only when you truly need database-specific power.

常见问题解答

「使用 @Query 编写 JPQL 与原生查询」课时是免费的吗?

是的 — 「使用 @Query 编写 JPQL 与原生查询」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Spring Boot 4 Complete Guide 课程的其余内容,请升级到 CoddyKit PRO。 Spring Boot 4 Complete Guide 课程共包含 4 节课。

「使用 @Query 编写 JPQL 与原生查询」这节课中我会学到什么?

编写明确的 JPQL 和原生 SQL 查询,绑定命名参数和位置参数,并映射投影。 你通过在浏览器中直接运行的动手代码来练习 Spring Boot 4 Complete Guide,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Spring Boot 4 Complete Guide 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Spring Boot 4 Complete Guide 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。

「使用 @Query 编写 JPQL 与原生查询」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Spring Boot 4 Complete Guide 课中编写并运行代码吗?

能。每节 Spring Boot 4 Complete Guide 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 派生查询方法与关键字解析
  2. 使用 @Query 编写 JPQL 与原生查询
  3. 规范与基于条件的动态过滤
  4. 分页、排序与切片流式处理
← 返回 Spring Boot 4 Complete Guide