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

Как отладить HQL-запрос

2.0 Middle🔥 131 комментариев
#ORM и Hibernate#Тестирование

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

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

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

# Как отладить HQL-запрос

HQL (Hibernate Query Language)

HQL - это объектно-ориентированный язык запросов, похожий на SQL, но работающий с сущностями вместо таблиц. Отладка HQL требует понимания как самого запроса, так и его преобразования в SQL.

1. Включение логирования SQL

application.properties

# Включаем логирование SQL
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.use_sql_comments=true

# Логирование параметров
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE

application.yml

spring:
  jpa:
    show-sql: true
    properties:
      hibernate:
        format_sql: true
        use_sql_comments: true

logging:
  level:
    org.hibernate.SQL: DEBUG
    org.hibernate.type.descriptor.sql.BasicBinder: TRACE

2. Просмотр сгенерированного SQL

@Service
public class UserService {
    private final UserRepository repository;
    private static final Logger logger = LoggerFactory.getLogger(UserService.class);
    
    public UserService(UserRepository repository) {
        this.repository = repository;
    }
    
    public List<User> findByName(String name) {
        // HQL запрос
        // После включения логирования увидим сгенерированный SQL в консоли
        List<User> users = repository.findByNameContaining(name);
        return users;
    }
}

// Вывод консоли будет примерно таким:
// select user0_.id as id1_0_, user0_.name as name2_0_, user0_.email as email3_0_ 
// from users user0_ 
// where user0_.name like ?
// Binding parameter [1] as [VARCHAR] - [%John%]

3. Использование StatelessSession для отладки

@Service
public class DebugQueryService {
    private final SessionFactory sessionFactory;
    
    public DebugQueryService(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }
    
    public void debugHQLQuery() {
        try (Session session = sessionFactory.openSession()) {
            String hql = "FROM User u WHERE u.email = :email ORDER BY u.id";
            
            // Создаём query и выполняем
            Query<User> query = session.createQuery(hql, User.class);
            query.setParameter("email", "john@example.com");
            
            List<User> users = query.list();
            System.out.println("Результаты: " + users.size());
        }
    }
}

4. Spring Data JPA - логирование через @Query

public interface UserRepository extends JpaRepository<User, Long> {
    
    // Явный HQL запрос
    @Query("SELECT u FROM User u WHERE u.email = :email")
    Optional<User> findByEmailHQL(@Param("email") String email);
    
    // Derived query (Spring генерирует HQL автоматически)
    List<User> findByNameContainingAndEmailLike(String name, String email);
}

@Service
public class UserService {
    private final UserRepository repository;
    
    public UserService(UserRepository repository) {
        this.repository = repository;
    }
    
    public void debugQueries() {
        // При выполнении эти запросы будут залогированы
        Optional<User> user = repository.findByEmailHQL("john@example.com");
        List<User> users = repository.findByNameContainingAndEmailLike("John", "%gmail%");
    }
}

5. Использование Query.getQueryString() и Query.getReturnedClass()

@Service
public class HQLDebugService {
    private final SessionFactory sessionFactory;
    private static final Logger logger = LoggerFactory.getLogger(HQLDebugService.class);
    
    public HQLDebugService(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }
    
    public void debugQuery() {
        try (Session session = sessionFactory.openSession()) {
            String hql = "SELECT u.id, u.name, COUNT(o) as orderCount " +
                         "FROM User u LEFT JOIN u.orders o " +
                         "WHERE u.active = true " +
                         "GROUP BY u.id, u.name " +
                         "ORDER BY u.id";
            
            Query<?> query = session.createQuery(hql);
            
            // Получаем информацию о запросе
            logger.debug("Исходный HQL: {}", hql);
            
            // Выполняем и получаем результаты
            List<?> results = query.list();
            logger.debug("Результатов: {}", results.size());
            
            // Логируем каждый результат
            for (Object result : results) {
                logger.debug("Результат: {}", result);
            }
        }
    }
}

6. Профилирование с Hibernate Statistics

@Configuration
public class HibernateConfig {
    
    @Bean
    public HibernatePropertiesCustomizer hibernatePropertiesCustomizer() {
        return props -> props.put("hibernate.generate_statistics", true);
    }
}

@Service
public class StatisticsService {
    private final SessionFactory sessionFactory;
    private static final Logger logger = LoggerFactory.getLogger(StatisticsService.class);
    
    public StatisticsService(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }
    
    public void analyzeQueryPerformance() {
        Statistics stats = sessionFactory.getStatistics();
        stats.clear();
        
        try (Session session = sessionFactory.openSession()) {
            Query<User> query = session.createQuery("FROM User u WHERE u.id > :id", User.class);
            query.setParameter("id", 100L);
            List<User> users = query.list();
            
            // Выводим статистику
            logger.info("===== Hibernate Statistics =====");
            logger.info("Prepare statement count: {}", stats.getPrepareStatementCount());
            logger.info("Execute statement count: {}", stats.getExecutionStatementCount());
            logger.info("Session open count: {}", stats.getSessionOpenCount());
            logger.info("Flush count: {}", stats.getFlushCount());
            logger.info("Query execution count: {}", stats.getQueryExecutionCount());
            logger.info("Entity insert count: {}", stats.getEntityInsertCount());
            logger.info("Entity update count: {}", stats.getEntityUpdateCount());
        }
    }
}

7. Отладка JOIN и FETCH

public interface OrderRepository extends JpaRepository<Order, Long> {
    
    // Проблема: N+1 SELECT
    // Для каждого заказа будет отдельный запрос пользователя
    @Query("SELECT o FROM Order o WHERE o.status = :status")
    List<Order> findByStatusNPlus1(@Param("status") String status);
    
    // Решение: FETCH JOIN
    @Query("SELECT DISTINCT o FROM Order o " +
           "LEFT JOIN FETCH o.user u " +
           "LEFT JOIN FETCH o.items " +
           "WHERE o.status = :status")
    List<Order> findByStatusOptimized(@Param("status") String status);
}

8. Использование Hibernate Interceptor для отладки

@Component
public class HibernateDebugInterceptor extends EmptyInterceptor {
    private static final Logger logger = LoggerFactory.getLogger(HibernateDebugInterceptor.class);
    
    @Override
    public String onPrepareStatement(String sql) {
        logger.debug("Выполняемый SQL: {}", sql);
        return sql;
    }
    
    @Override
    public int[] findDirty(Object entity, Serializable id, Object[] currentState, 
                           Object[] previousState, String[] propertyNames, Type[] types) {
        logger.debug("Entity {} изменился", entity.getClass().getSimpleName());
        return super.findDirty(entity, id, currentState, previousState, propertyNames, types);
    }
}

@Configuration
public class HibernateInterceptorConfig {
    
    @Bean
    public HibernatePropertiesCustomizer hibernateInterceptor(HibernateDebugInterceptor interceptor) {
        return props -> props.put("hibernate.session_factory.interceptor", interceptor);
    }
}

9. Использование Query.uniqueResult() и Query.list() для проверки

@Service
public class QueryValidationService {
    private final SessionFactory sessionFactory;
    private static final Logger logger = LoggerFactory.getLogger(QueryValidationService.class);
    
    public QueryValidationService(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }
    
    public User findUserById(Long id) {
        try (Session session = sessionFactory.openSession()) {
            // Запрос с ожиданием одного результата
            User user = session.createQuery(
                "FROM User u WHERE u.id = :id",
                User.class
            ).setParameter("id", id).uniqueResult();
            
            if (user == null) {
                logger.warn("User с id {} не найден", id);
            } else {
                logger.debug("User найден: {}", user.getName());
            }
            return user;
        }
    }
    
    public List<User> findActiveUsers() {
        try (Session session = sessionFactory.openSession()) {
            List<User> users = session.createQuery(
                "FROM User u WHERE u.active = true ORDER BY u.id",
                User.class
            ).list();
            
            logger.debug("Найдено {} активных пользователей", users.size());
            return users;
        }
    }
}

10. Тестирование HQL запросов

@DataJpaTest
public class UserRepositoryTest {
    
    @Autowired
    private UserRepository repository;
    
    @Autowired
    private TestEntityManager entityManager;
    
    @Test
    public void testFindByEmailHQL() {
        // Arrange
        User user = new User("john@example.com", "John");
        entityManager.persistAndFlush(user);
        entityManager.clear();
        
        // Act
        Optional<User> found = repository.findByEmailHQL("john@example.com");
        
        // Assert
        assertTrue(found.isPresent());
        assertEquals("John", found.get().getName());
    }
}

Best Practices отладки HQL

  1. Всегда включай form_sql при разработке
  2. Логируй параметры запроса (TRACE level)
  3. Используй @Query для явного контроля
  4. Избегай N+1 SELECT - используй FETCH JOIN
  5. Профилируй с помощью Hibernate Statistics
  6. Тестируй запросы в unit тестах
  7. Проверяй generated SQL в консоли
  8. Используй DISTINCT при FETCH JOIN коллекций
  9. Мониторь производительность в боевых окружениях
  10. Документируй сложные запросы с комментариями