Что такое native в @Query для Hibernate?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
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 когда это действительно необходимо.