Когда и зачем вы используете нативные SQL-запросы в Java-приложениях?

«Когда и зачем вы используете нативные SQL-запросы в Java-приложениях?» — вопрос из категории Базы данных, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Нативные SQL-запросы используются, когда возможности ORM (например, Hibernate) или JPA Criteria API недостаточны для сложных операций, требующих максимальной производительности или специфичного синтаксиса СУБД.

Основные сценарии использования:

  1. Сложные аналитические запросы: Оконные функции (OVER, PARTITION BY), рекурсивные CTE.
  2. Массовые операции (Batch Update): UPDATE ... WHERE ... для тысяч строк, что эффективнее поэтапного сохранения сущностей.
  3. Специфичные оптимизации: Использование подсказок для оптимизатора (/*+ INDEX(...) */ в Oracle), FOR UPDATE SKIP LOCKED.

Примеры выполнения:

1. Через JPA EntityManager (с маппингом на сущность):

@PersistenceContext
private EntityManager em;

public List<User> findActiveUsersNative() {
    String sql = "SELECT * FROM app_user WHERE is_active = true";
    Query query = em.createNativeQuery(sql, User.class); // User должен быть @Entity
    return query.getResultList();
}

2. Через Spring JdbcTemplate (для проекций или DTO):

@Autowired
private JdbcTemplate jdbcTemplate;

public List<UserStatsDto> getUserStatistics() {
    String sql = """
        SELECT department_id, 
               COUNT(*) as user_count, 
               AVG(salary) as avg_salary
        FROM employee
        GROUP BY department_id
        """;

    return jdbcTemplate.query(sql, (rs, rowNum) -> 
        new UserStatsDto(
            rs.getLong("department_id"),
            rs.getInt("user_count"),
            rs.getBigDecimal("avg_salary")
        )
    );
}

Критически важные правила:

  • Всегда используйте параметризацию (? или :name) для предотвращения SQL-инъекций. Никогда не конкатенируйте пользовательский ввод в строку запроса.
  • Помните о переносимости: Нативный запрос, использующий специфичный для PostgreSQL синтаксис (например, ILIKE), не будет работать на MySQL.
  • Учитывайте кэш второго уровня Hibernate: Результаты нативных запросов могут не обновлять кэш сущностей, что приводит к несогласованности данных.