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

Как проверяешь свои запросы в БД

2.0 Middle🔥 91 комментариев
#Другое

Комментарии (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. Мой процесс проверки запросов

  1. Логирование (application.properties)

    • Вижу SQL в консоли
    • Проверяю параметры binding
  2. Unit тесты (H2 база)

    • Тестирую логику запросов
    • Быстро выполняются
  3. Интеграционные тесты (реальная БД)

    • Проверяю на реальной БД
    • Ловлю проблемы совместимости
  4. EXPLAIN ANALYZE (в prod-like окружении)

    • Анализирую план выполнения
    • Вижу нужны ли индексы
  5. Профилирование (Production)

    • Мониторю через DataDog/New Relic
    • Ловлю slow queries
    • Оптимизирую bottleneck'и

Best Practices

  1. Всегда логируй SQL в development
  2. Тестируй на реальной БД для интеграционных тестов
  3. Используй EXPLAIN ANALYZE перед production
  4. Проверяй на N+1 через логирование запросов
  5. Индексируй правильно — посмотри план выполнения
  6. Используй типобезопасные запросы (Criteria API, QueryDSL)
  7. Кешируй результаты если часто выполняются