Комментарии (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
- Всегда включай form_sql при разработке
- Логируй параметры запроса (TRACE level)
- Используй @Query для явного контроля
- Избегай N+1 SELECT - используй FETCH JOIN
- Профилируй с помощью Hibernate Statistics
- Тестируй запросы в unit тестах
- Проверяй generated SQL в консоли
- Используй DISTINCT при FETCH JOIN коллекций
- Мониторь производительность в боевых окружениях
- Документируй сложные запросы с комментариями