← Назад к вопросам

Что такое native в @Query для Hibernate?

2.0 Middle🔥 151 комментариев
#SOLID и паттерны проектирования#Spring Boot и Spring Data

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Native в @Query для Hibernate

@Query(nativeQuery = true) — это параметр в Spring Data JPA, который позволяет выполнять native SQL запросы (написанные непосредственно на SQL диалекте БД) вместо JPQL (Java Persistence Query Language). По умолчанию nativeQuery = false, что означает использование JPQL (ORM абстракция).

Отличие между JPQL и Native SQL

JPQL (Object-Oriented Query Language):

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    // JPQL - работает с объектами/сущностями (nativeQuery = false по умолчанию)
    @Query("SELECT u FROM User u WHERE u.email = :email AND u.active = true")
    User findActiveUserByEmail(@Param("email") String email);
}

Native SQL (Database-specific Query Language):

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    // Native SQL - работает напрямую с SQL БД
    @Query(value = "SELECT * FROM users WHERE email = :email AND active = true", 
           nativeQuery = true)
    User findActiveUserByEmail(@Param("email") String email);
}

Синтаксис @Query с nativeQuery = true

Простой SELECT:

@Query(value = "SELECT u.* FROM users u WHERE u.id = :userId", nativeQuery = true)
User findUserById(@Param("userId") Long userId);

// Или если возвращаем DTO/массив
@Query(value = "SELECT u.id, u.email, u.first_name FROM users u WHERE u.status = :status", 
       nativeQuery = true)
List<Object[]> findUsersByStatus(@Param("status") String status);

Использование параметров:

// Именованные параметры (рекомендуется)
@Query(value = "SELECT * FROM users u WHERE u.email = :email AND u.created_at > :createdAfter",
       nativeQuery = true)
List<User> findUsersByEmailAndDate(
    @Param("email") String email,
    @Param("createdAfter") LocalDateTime createdAfter
);

// Позиционные параметры (менее читаемо)
@Query(value = "SELECT * FROM users WHERE email = ? AND age > ?", nativeQuery = true)
List<User> findUsersByEmailAndAge(String email, int age);

Примеры Native Query

1. SELECT с WHERE:

@Query(value = "SELECT * FROM orders WHERE user_id = :userId AND status = :status",
       nativeQuery = true)
List<Order> findOrdersByUserAndStatus(
    @Param("userId") Long userId,
    @Param("status") String status
);

2. SELECT с JOIN:

@Query(value = 
    "SELECT u.id, u.email, o.id as order_id, o.total " +
    "FROM users u " +
    "INNER JOIN orders o ON u.id = o.user_id " +
    "WHERE u.active = true AND o.total > :minAmount",
    nativeQuery = true)
List<Object[]> findActiveUsersWithHighOrders(@Param("minAmount") Double minAmount);

3. SELECT с GROUP BY и HAVING:

@Query(value = 
    "SELECT u.id, u.email, COUNT(o.id) as order_count " +
    "FROM users u " +
    "LEFT JOIN orders o ON u.id = o.user_id " +
    "GROUP BY u.id, u.email " +
    "HAVING COUNT(o.id) > :minOrders " +
    "ORDER BY order_count DESC",
    nativeQuery = true)
List<Object[]> findUsersWithMinimumOrders(@Param("minOrders") int minOrders);

4. SELECT с подзапросом:

@Query(value = 
    "SELECT u.* FROM users u " +
    "WHERE u.id IN (" +
    "  SELECT DISTINCT user_id FROM orders WHERE total > :amount" +
    ")",
    nativeQuery = true)
List<User> findUsersWithHighValueOrders(@Param("amount") Double amount);

UPDATE с Native Query

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    
    @Modifying
    @Transactional
    @Query(value = "UPDATE users SET active = false WHERE last_login < :date",
           nativeQuery = true)
    int deactivateInactiveUsers(@Param("date") LocalDateTime date);
    
    @Modifying
    @Transactional
    @Query(value = "UPDATE orders SET status = :newStatus WHERE status = :oldStatus AND created_at < :date",
           nativeQuery = true)
    int updateOrderStatus(
        @Param("oldStatus") String oldStatus,
        @Param("newStatus") String newStatus,
        @Param("date") LocalDateTime date
    );
}

DELETE с Native Query

@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
    
    @Modifying
    @Transactional
    @Query(value = "DELETE FROM orders WHERE status = :status AND created_at < :date",
           nativeQuery = true)
    int deleteOldOrdersByStatus(
        @Param("status") String status,
        @Param("date") LocalDateTime date
    );
}

Преимущества Native SQL

1. Производительность - прямое управление SQL:

// Native SQL позволяет оптимизировать сложные запросы
@Query(value = 
    "SELECT u.id, u.email, " +
    "       COUNT(o.id) as order_count, " +
    "       SUM(o.total) as total_spent " +
    "FROM users u " +
    "LEFT JOIN orders o ON u.id = o.user_id " +
    "WHERE u.created_at > :date " +
    "GROUP BY u.id " +
    "HAVING SUM(o.total) > :minSpent " +
    "ORDER BY total_spent DESC " +
    "LIMIT :limit",
    nativeQuery = true)
List<Object[]> findTopSpendersNative(
    @Param("date") LocalDateTime date,
    @Param("minSpent") Double minSpent,
    @Param("limit") int limit
);

2. Поддержка специфичных для БД функций:

// PostgreSQL JSON функции
@Query(value = "SELECT id, email, data->>'country' as country " +
               "FROM users WHERE data->>'country' = :country",
       nativeQuery = true)
List<Object[]> findUsersByCountry(@Param("country") String country);

// MySQL FULL TEXT SEARCH
@Query(value = "SELECT * FROM products " +
               "WHERE MATCH(name, description) AGAINST(:searchTerm IN BOOLEAN MODE)",
       nativeQuery = true)
List<Product> searchProducts(@Param("searchTerm") String searchTerm);

3. Сложные операции, которые сложно выразить в JPQL:

// Window functions (PostgreSQL)
@Query(value = 
    "SELECT id, email, salary, " +
    "       ROW_NUMBER() OVER (ORDER BY salary DESC) as salary_rank, " +
    "       LAG(salary) OVER (ORDER BY salary DESC) as prev_salary " +
    "FROM users",
    nativeQuery = true)
List<Object[]> getUsersWithRank();

// Common Table Expressions (CTE)
@Query(value = 
    "WITH RECURSIVE hierarchy AS (" +
    "  SELECT id, name, parent_id, 0 as level FROM categories WHERE parent_id IS NULL " +
    "  UNION ALL " +
    "  SELECT c.id, c.name, c.parent_id, h.level + 1 " +
    "  FROM categories c JOIN hierarchy h ON c.parent_id = h.id " +
    ") " +
    "SELECT * FROM hierarchy",
    nativeQuery = true)
List<Object[]> getCategoryHierarchy();

Недостатки Native SQL

1. Потеря абстракции - зависимость от SQL диалекта:

// Этот запрос работает в PostgreSQL, но может не работать в MySQL
@Query(value = "SELECT * FROM users OFFSET :offset LIMIT :limit",
       nativeQuery = true)
List<User> getPagePostgres(@Param("offset") int offset, @Param("limit") int limit);

// Вместо database-agnostic JPQL
@Query("SELECT u FROM User u ORDER BY u.id")
Page<User> getPage(Pageable pageable);

2. Отсутствие ORM преимуществ:

// Native SQL возвращает Object[], не сущности
@Query(value = "SELECT u.*, o.* FROM users u JOIN orders o ON u.id = o.user_id",
       nativeQuery = true)
List<Object[]> findUsersWithOrders(); // Object[] - сложно работать

// JPQL автоматически маппит в сущности с отношениями
@Query("SELECT u FROM User u JOIN u.orders o")
List<User> findUsersWithOrders(); // User с loaded orders

Маппинг результатов Native Query

1. Маппинг на сущность (если результат полный):

@Query(value = "SELECT * FROM users WHERE active = true", nativeQuery = true)
List<User> findActiveUsers(); // Прямо на User (all columns)

2. Маппинг на DTO через конструктор:

public record UserDTO(
    Long id,
    String email,
    int orderCount
) {}

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    
    @Query(value = 
        "SELECT u.id, u.email, COUNT(o.id) as order_count " +
        "FROM users u " +
        "LEFT JOIN orders o ON u.id = o.user_id " +
        "GROUP BY u.id, u.email",
        nativeQuery = true)
    List<UserDTO> findUsersWithOrderCount();
}

3. Маппинг на объект с @SqlResultSetMapping (Hibernate):

@Entity
@SqlResultSetMapping(
    name = "UserOrderMapping",
    classes = @ConstructorResult(
        targetClass = UserOrderDTO.class,
        columns = {
            @ColumnResult(name = "userId"),
            @ColumnResult(name = "email"),
            @ColumnResult(name = "orderCount", type = Integer.class)
        }
    )
)
public class User {
    // ...
}

public record UserOrderDTO(Long userId, String email, int orderCount) {}

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    
    @Query(value = 
        "SELECT u.id as userId, u.email, COUNT(o.id) as orderCount " +
        "FROM users u LEFT JOIN orders o ON u.id = o.user_id " +
        "GROUP BY u.id, u.email",
        resultSetMapping = "UserOrderMapping",
        nativeQuery = true)
    List<UserOrderDTO> findUsersWithOrders();
}

Best Practices

1. Используй именованные параметры, а не позиционные:

// ✅ Правильно
@Query(value = "SELECT * FROM users WHERE email = :email AND status = :status",
       nativeQuery = true)
User find(@Param("email") String email, @Param("status") String status);

// ❌ Плохо
@Query(value = "SELECT * FROM users WHERE email = ? AND status = ?",
       nativeQuery = true)
User find(String email, String status);

2. Используй @Modifying для UPDATE/DELETE:

@Modifying
@Transactional
@Query(value = "UPDATE users SET active = false WHERE last_login < :date",
       nativeQuery = true)
int deactivate(@Param("date") LocalDateTime date);

3. Документируй зависимость от SQL диалекта:

// PostgreSQL specific
@Query(value = "SELECT DISTINCT ON (u.id) * FROM users u ORDER BY u.id, u.created_at DESC",
       nativeQuery = true)
List<User> getLatestUserVersions();

4. Предпочитай JPQL когда возможно:

// JPQL - database agnostic
@Query("SELECT u FROM User u WHERE u.active = true ORDER BY u.createdAt DESC")
List<User> findActiveUsers();

Заключение

Native Query в @Query полезна для оптимизации производительности, использования специфичных для БД функций и реализации сложных запросов. Однако следует использовать её осторожно, предпочитая JPQL для портативности и безопасности, и только прибегая к Native SQL когда это действительно необходимо.