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

Что такое Criteria API?

2.3 Middle🔥 111 комментариев
#ORM и Hibernate

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

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

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

Criteria API в Hibernate/JPA

Criteria API — это type-safe способ построения динамических SQL запросов в Hibernate. Это альтернатива HQL и нативному SQL с лучшей типизацией и удобством.

Что это и зачем нужна

Проблема с HQL и SQL:

// HQL — строки, нет type-safety
public List<User> findActiveUsers(String role) {
    String hql = "FROM User WHERE active = true AND role = ?";
    return session.createQuery(hql, User.class)
        .setParameter(1, role)
        .getResultList();
    // Если ошибка в HQL — узнаем только при runtime!
}

// SQL — то же самое
public List<User> findActiveUsers(String role) {
    String sql = "SELECT * FROM users WHERE active = true AND role = ?";
    return session.createNativeQuery(sql, User.class)
        .setParameter(1, role)
        .getResultList();
}

Решение — Criteria API:

public List<User> findActiveUsers(String role) {
    CriteriaBuilder cb = entityManager.getCriteriaBuilder();
    CriteriaQuery<User> query = cb.createQuery(User.class);
    Root<User> root = query.from(User.class);
    
    query.select(root)
        .where(
            cb.and(
                cb.isTrue(root.get(User_.active)),
                cb.equal(root.get(User_.role), role)
            )
        );
    
    return entityManager.createQuery(query).getResultList();
}

Преимущества:

  • Compile-time type checking (используем User_.active, а не строка)
  • IDE автодополнение
  • Безопаснее рефакторить
  • Динамическое построение условий

Основные компоненты

1. CriteriaBuilder — фабрика для построения запроса 2. CriteriaQuery — сам запрос 3. Root — главная таблица (FROM) 4. Predicate — условия (WHERE)

Пример 1: простой SELECT

public List<User> findAllUsers() {
    CriteriaBuilder cb = entityManager.getCriteriaBuilder();
    CriteriaQuery<User> query = cb.createQuery(User.class);
    Root<User> root = query.from(User.class);
    
    query.select(root);
    
    return entityManager.createQuery(query).getResultList();
}

Пример 2: WHERE с условиями

public List<User> findUsers(String role, boolean active, int minAge) {
    CriteriaBuilder cb = entityManager.getCriteriaBuilder();
    CriteriaQuery<User> query = cb.createQuery(User.class);
    Root<User> root = query.from(User.class);
    
    List<Predicate> predicates = new ArrayList<>();
    
    if (role != null) {
        predicates.add(cb.equal(root.get(User_.role), role));
    }
    
    predicates.add(cb.equal(root.get(User_.active), active));
    predicates.add(cb.greaterThanOrEqualTo(root.get(User_.age), minAge));
    
    query.select(root)
        .where(cb.and(predicates.toArray(new Predicate[0])));
    
    return entityManager.createQuery(query).getResultList();
}

Пример 3: JOIN и связи

// Найти всех пользователей с активными заказами
public List<User> findUsersWithActiveOrders() {
    CriteriaBuilder cb = entityManager.getCriteriaBuilder();
    CriteriaQuery<User> query = cb.createQuery(User.class);
    Root<User> userRoot = query.from(User.class);
    
    // JOIN с таблицей заказов
    Join<User, Order> orderJoin = userRoot.join(User_.orders, JoinType.INNER);
    
    query.select(userRoot)
        .where(cb.isTrue(orderJoin.get(Order_.active)))
        .distinct(true);
    
    return entityManager.createQuery(query).getResultList();
}

Пример 4: Aggregation (COUNT, SUM, AVG)

public long countActiveUsers() {
    CriteriaBuilder cb = entityManager.getCriteriaBuilder();
    CriteriaQuery<Long> query = cb.createQuery(Long.class);
    Root<User> root = query.from(User.class);
    
    query.select(cb.count(root))
        .where(cb.isTrue(root.get(User_.active)));
    
    return entityManager.createQuery(query).getSingleResult();
}

public double getAverageOrderAmount() {
    CriteriaBuilder cb = entityManager.getCriteriaBuilder();
    CriteriaQuery<Double> query = cb.createQuery(Double.class);
    Root<Order> root = query.from(Order.class);
    
    query.select(cb.avg(root.get(Order_.amount)));
    
    return entityManager.createQuery(query).getSingleResult();
}

Пример 5: GROUP BY и HAVING

public List<OrderStatistics> getOrderStatsByUser() {
    CriteriaBuilder cb = entityManager.getCriteriaBuilder();
    CriteriaQuery<OrderStatistics> query = cb.createQuery(OrderStatistics.class);
    Root<Order> orderRoot = query.from(Order.class);
    
    query.multiselect(
        orderRoot.get(Order_.userId),
        cb.count(orderRoot),
        cb.sum(orderRoot.get(Order_.amount))
    )
    .groupBy(orderRoot.get(Order_.userId))
    .having(cb.gt(cb.count(orderRoot), 5)); // Более 5 заказов
    
    return entityManager.createQuery(query).getResultList();
}

Пример 6: ORDER BY

public List<User> findUsersOrderedByName(String role) {
    CriteriaBuilder cb = entityManager.getCriteriaBuilder();
    CriteriaQuery<User> query = cb.createQuery(User.class);
    Root<User> root = query.from(User.class);
    
    query.select(root)
        .where(cb.equal(root.get(User_.role), role))
        .orderBy(
            cb.asc(root.get(User_.name)),
            cb.desc(root.get(User_.createdAt))
        );
    
    return entityManager.createQuery(query).getResultList();
}

Важное: Metamodel (User_)

Для type-safe access к атрибутам используется Metamodel:

// Аннотированный класс User с @Entity
@Entity
public class User {
    @Id
    private Long id;
    private String name;
    private String role;
    private boolean active;
    // ...
}

// Автоматически генерируется Metamodel
@StaticMetamodel(User.class)
public class User_ {
    public static volatile SingularAttribute<User, Long> id;
    public static volatile SingularAttribute<User, String> name;
    public static volatile SingularAttribute<User, String> role;
    public static volatile SingularAttribute<User, Boolean> active;
    // ...
}

Это генерируется процессором аннотаций (Hibernate JPA metamodel generator).

Когда использовать Criteria API

Хорошо использовать:

  • Сложные динамические запросы с множеством условий
  • Построение запросов в runtime на основе пользовательских фильтров
  • Когда нужна type-safety
  • Запросы с множеством JOIN'ов и условий

Не рекомендуется:

  • Простые запросы (лучше использовать Spring Data методы)
  • Очень сложные запросы (нативный SQL будет понятнее)
  • Queries с специфичным синтаксисом БД

Практика

@Repository
public class UserRepository {
    @PersistenceContext
    private EntityManager em;
    
    public List<User> search(UserSearchCriteria criteria) {
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<User> query = cb.createQuery(User.class);
        Root<User> root = query.from(User.class);
        
        List<Predicate> predicates = new ArrayList<>();
        
        if (criteria.getRole() != null) {
            predicates.add(cb.equal(root.get(User_.role), criteria.getRole()));
        }
        
        if (criteria.getNamePattern() != null) {
            predicates.add(cb.like(
                cb.lower(root.get(User_.name)),
                "%" + criteria.getNamePattern().toLowerCase() + "%"
            ));
        }
        
        if (criteria.getMinAge() != null) {
            predicates.add(cb.ge(root.get(User_.age), criteria.getMinAge()));
        }
        
        query.select(root)
            .where(cb.and(predicates.toArray(new Predicate[0])))
            .orderBy(cb.asc(root.get(User_.name)));
        
        return em.createQuery(query)
            .setFirstResult(criteria.getOffset())
            .setMaxResults(criteria.getLimit())
            .getResultList();
    }
}

Criteria API мощный инструмент, особенно когда нужна гибкость в построении запросов без потери типизации.

Что такое Criteria API? | PrepBro