Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# Проверка SQL запросов в Java приложениях
1. Логирование Hibernate/JPA запросов
Вариант 1: 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
Вариант 2: logback.xml
<configuration>
<logger name="org.hibernate.SQL" level="DEBUG"/>
<logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="TRACE"/>
</configuration>
Output
select u1_0.id, u1_0.name, u1_0.email from users u1_0 where u1_0.id=?
-- с параметром binding parameter [1] as [BIGINT] - [123]
2. Статический анализ: Hibernate Query Validation
@Entity
public class User {
@Id
private Long id;
private String name;
private String email;
}
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// ❌ Опечатка в имени поля — ошибка при запуске
@Query("SELECT u FROM User u WHERE u.nam = ?1")
User findByNameWrong(String name);
// ✅ Правильный запрос
@Query("SELECT u FROM User u WHERE u.name = ?1")
User findByName(String name);
}
IDE подчеркнёт ошибку если включена правильная конфигурация.
3. Тестирование запросов: Unit Test
Вариант 1: In-Memory база (H2, Derby)
@SpringBootTest
@AutoConfigureTestDatabase(replace = Replace.ANY) // Используем H2
public class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
@Autowired
private TestEntityManager testEntityManager;
@Test
public void testFindByName() {
// Arrange
User user = User.builder()
.name("Alice")
.email("alice@example.com")
.build();
testEntityManager.persistAndFlush(user);
// Act
User found = userRepository.findByName("Alice");
// Assert
assertThat(found).isNotNull();
assertThat(found.getName()).isEqualTo("Alice");
}
}
Вариант 2: Реальная база для интеграционных тестов
@SpringBootTest
@AutoConfigureTestDatabase(replace = Replace.NONE) // Используем реальную БД
public class UserRepositoryIntegrationTest {
@Autowired
private UserRepository userRepository;
@Test
public void testComplexQuery() {
// Тестируем реальный запрос в PostgreSQL
List<User> users = userRepository.findActiveUsers();
assertThat(users).isNotEmpty();
}
@Test
public void testQueryPerformance() {
long startTime = System.currentTimeMillis();
List<User> users = userRepository.findAll();
long duration = System.currentTimeMillis() - startTime;
// Проверяем что запрос быстрый
assertThat(duration).isLessThan(1000);
}
}
4. Explain Plan (анализ выполнения запроса)
public class QueryAnalyzer {
public void analyzeQuery() {
String sql = "SELECT * FROM users WHERE id = 1";
// Получаем план выполнения
String explainPlan = "EXPLAIN ANALYZE " + sql;
// Отправляем в БД
nativeQuery(explainPlan);
// Output:
// Seq Scan on users (cost=0.00..35.50 rows=1 width=100)
// Filter: (id = 1)
// Actual time=0.123..0.125 rows=1
}
}
Практический пример в коде
@Service
public class UserService {
@PersistenceContext
private EntityManager entityManager;
public List<User> findUsersWithExplain() {
// Выполняем EXPLAIN перед основным запросом
Query explainQuery = entityManager.createNativeQuery(
"EXPLAIN ANALYZE SELECT u FROM users u WHERE u.active = true"
);
List<?> plan = explainQuery.getResultList();
plan.forEach(System.out::println);
// Теперь выполняем основной запрос
Query query = entityManager.createQuery(
"SELECT u FROM User u WHERE u.active = true",
User.class
);
return query.getResultList();
}
}
5. Профилирование запросов: Batch операции
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
// ❌ N+1 query проблема
public List<UserDTO> getBadUsers() {
List<User> users = userRepository.findAll(); // 1 запрос
return users.stream()
.map(u -> new UserDTO(
u.getId(),
u.getName(),
u.getOrders().size() // N дополнительных запросов!
))
.collect(Collectors.toList());
}
// ✅ Fetch join (один запрос)
public List<UserDTO> getGoodUsers() {
List<User> users = userRepository.findAllWithOrders(); // 1 запрос с join
return users.stream()
.map(u -> new UserDTO(
u.getId(),
u.getName(),
u.getOrders().size()
))
.collect(Collectors.toList());
}
}
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
@Query("SELECT DISTINCT u FROM User u LEFT JOIN FETCH u.orders")
List<User> findAllWithOrders();
}
6. Инструменты мониторинга запросов
P6Spy (логирование с параметрами)
<dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
<version>3.9.1</version>
</dependency>
spring.datasource.url=jdbc:p6spy:postgresql://localhost:5432/mydb
spring.datasource.driver-class-name=com.p6spy.engine.spy.P6SpyDriver
Output:
2024-01-15 10:30:45 | took 5ms | statement |
SELECT u.id, u.name FROM users u WHERE u.id = 1
Datasource Proxy
<dependency>
<groupId>net.ttddyy</groupId>
<artifactId>datasource-proxy</artifactId>
<version>1.8</version>
</dependency>
7. Тестирование производительности запросов
@SpringBootTest
public class QueryPerformanceTest {
@Autowired
private UserRepository userRepository;
@Test
public void shouldFindUsersFast() {
// Arrange
IntStream.range(0, 10000).forEach(i -> {
userRepository.save(User.builder()
.name("User" + i)
.email("user" + i + "@example.com")
.build());
});
// Act
long startTime = System.nanoTime();
List<User> users = userRepository.findAll();
long duration = System.nanoTime() - startTime;
// Assert
assertThat(duration).isLessThan(5_000_000_000L); // < 5 секунд
assertThat(users).hasSize(10000);
}
}
8. Debugging запросов: JDBC logging
@Configuration
public class JdbcDebugConfig {
@Bean
public DataSource dataSource(DataSource dataSource) {
return new DataSourceProxyLoggerImpl(
dataSource,
new JdbcExecutionLogger()
);
}
}
9. Статический анализ: QueryDSL
// Вместо строк — типобезопасные запросы
JPAQuery<User> query = new JPAQuery<>(entityManager);
QUser quser = QUser.user;
List<User> users = query.select(quser)
.from(quser)
.where(quser.name.eq("Alice"))
.fetch();
// Ошибка на этапе компиляции, а не runtime
10. Мой процесс проверки запросов
-
Логирование (application.properties)
- Вижу SQL в консоли
- Проверяю параметры binding
-
Unit тесты (H2 база)
- Тестирую логику запросов
- Быстро выполняются
-
Интеграционные тесты (реальная БД)
- Проверяю на реальной БД
- Ловлю проблемы совместимости
-
EXPLAIN ANALYZE (в prod-like окружении)
- Анализирую план выполнения
- Вижу нужны ли индексы
-
Профилирование (Production)
- Мониторю через DataDog/New Relic
- Ловлю slow queries
- Оптимизирую bottleneck'и
Best Practices
- Всегда логируй SQL в development
- Тестируй на реальной БД для интеграционных тестов
- Используй EXPLAIN ANALYZE перед production
- Проверяй на N+1 через логирование запросов
- Индексируй правильно — посмотри план выполнения
- Используй типобезопасные запросы (Criteria API, QueryDSL)
- Кешируй результаты если часто выполняются