@Query lets you write the query explicitly when a derived method won't do — and it comes in two flavours: JPQL (the default) and native SQL. The choice between them is about portability versus power.
JPQL — the default
JPQL is an object query language: you query entities and their fields, not tables and columns. Hibernate translates it to the dialect's SQL at runtime.
@Query("SELECT u FROM User u WHERE u.email = :email AND u.status = :status")
List<User> find(@Param("email") String email, @Param("status") Status status);
Note User (the entity) and u.email (a field), not users/email. Because Hibernate generates the SQL, JPQL is database-portable, participates fully in the persistence context (results are managed entities), respects entity mappings, and you can JOIN FETCH to solve N+1. You can bind parameters by name (:email + @Param) or by position (?1).
Native query — nativeQuery = true
When you need vendor SQL — window functions, CTEs, ON CONFLICT, full-text, hints — drop to native:
@Query(value = "SELECT * FROM users WHERE status = :status "
+ "ORDER BY created_at DESC LIMIT 10", nativeQuery = true)
List<User> recent(@Param("status") String status);
Native runs raw SQL against the database. The trade-offs: you reference tables and columns (not entities), it is not portable across vendors, and JPQL-only features like JOIN FETCH don't apply. You can still map rows back to a managed entity if the columns line up with the mapping, use a @SqlResultSetMapping, or — most commonly — map to a DTO/interface projection.
When to use which
Default to JPQL: portable, type-aware, returns managed entities, supports JOIN FETCH and @EntityGraph. Reach for native only when JPQL can't express what you need or you must use a database-specific feature for performance. If you're using native purely to return a flat shape, prefer a DTO projection over a native query so you keep portability.