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

Можно ли написать запрос оперируя сущностями, а не таблицами?

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

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

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

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

Можно ли написать запрос оперируя сущностями?

Да, это основная идея ORMObject-Relational Mapping. Вместо SQL ты работаешь с объектами на Java, а фреймворк генерирует SQL сам.

ORM-фреймворки в Java

1. Hibernate / JPA

Самый популярный вариант в Java-мире:

// JPA Entity
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue
    private Long id;
    
    @Column(name = "name")
    private String name;
    
    @Column(name = "email")
    private String email;
    
    @OneToMany(mappedBy = "user")
    private List<Order> orders;
}

@Entity
@Table(name = "orders")
public class Order {
    @Id
    @GeneratedValue
    private Long id;
    
    @ManyToOne
    @JoinColumn(name = "user_id")
    private User user;
    
    @Column(name = "amount")
    private BigDecimal amount;
}

// Запрос оперируя сущностями
public class UserRepository {
    @PersistenceContext
    private EntityManager em;
    
    // JPQL — запрос на объектах
    public List<User> findAllUsers() {
        return em.createQuery(
            "SELECT u FROM User u WHERE u.name LIKE :pattern",
            User.class
        ).setParameter("pattern", "%John%")
         .getResultList();
    }
    
    // Получение связанных объектов
    public List<Order> getUserOrders(Long userId) {
        return em.createQuery(
            "SELECT o FROM Order o WHERE o.user.id = :userId ORDER BY o.id DESC",
            Order.class
        ).setParameter("userId", userId)
         .getResultList();
    }
}

Что происходит под капотом:

JPQL: SELECT u FROM User u WHERE u.name LIKE :pattern
↓
Hibernate генерирует SQL:
SELECT user0_.id, user0_.name, user0_.email 
FROM users user0_ 
WHERE user0_.name LIKE ?

2. Criteria API

Программный способ без строк:

public class UserRepositoryWithCriteria {
    @PersistenceContext
    private EntityManager em;
    
    public List<User> findUsersByName(String pattern) {
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<User> query = cb.createQuery(User.class);
        Root<User> user = query.from(User.class);
        
        // Типобезопасно — IDE подскажет!
        query.where(cb.like(user.get(User_.name), pattern));
        query.orderBy(cb.asc(user.get(User_.id)));
        
        return em.createQuery(query).getResultList();
    }
    
    // Сложный запрос с join
    public List<User> findUsersWithOrders(BigDecimal minAmount) {
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<User> query = cb.createQuery(User.class);
        Root<User> user = query.from(User.class);
        Join<User, Order> orders = user.join("orders");
        
        query.where(cb.greaterThan(orders.get("amount"), minAmount));
        query.distinct(true);
        
        return em.createQuery(query).getResultList();
    }
}

Преимущество: типобезопасность, IDE подсказывает поля сущности.

3. Spring Data JPA

Ещё более удобный способ:

// Просто наследуемся, больше ничего не нужно
public interface UserRepository extends JpaRepository<User, Long> {
    // Spring Data генерирует реализацию автоматически!
    
    List<User> findByNameLike(String pattern);
    
    List<User> findByEmailEndingWith(String domain);
    
    // Сложный запрос через @Query
    @Query("SELECT u FROM User u JOIN u.orders o " +
           "WHERE o.amount > :minAmount")
    List<User> findUsersWithLargeOrders(@Param("minAmount") BigDecimal amount);
}

// Использование
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    
    public void printUsersByName(String pattern) {
        List<User> users = userRepository.findByNameLike(pattern);
        users.forEach(u -> System.out.println(u.getName()));
    }
}

4. QueryDSL

Ещё более типобезопасный способ с красивым синтаксисом:

// Генерируется Q-класс QUser из User сущности
public class UserRepositoryWithQueryDSL {
    private final JPAQueryFactory queryFactory;
    
    public UserRepositoryWithQueryDSL(EntityManager em) {
        this.queryFactory = new JPAQueryFactory(em);
    }
    
    public List<User> findUsersWithOrders(BigDecimal minAmount) {
        QUser user = QUser.user;
        QOrder order = QOrder.order;
        
        return queryFactory
            .select(user)
            .from(user)
            .join(user.orders, order)
            .where(order.amount.goe(minAmount))
            .orderBy(user.id.desc())
            .fetch();
    }
    
    // Чтение данных для DTO (без загрузки всех полей)
    public List<UserDTO> getUserDTOs() {
        QUser user = QUser.user;
        
        return queryFactory
            .select(Projections.constructor(
                UserDTO.class,
                user.id,
                user.name,
                user.email
            ))
            .from(user)
            .fetch();
    }
}

Проблемы и ограничения ORM

1. N+1 Query Problem

Осторожно с ленивой загрузкой!

// ❌ Плохо — 1 запрос за пользователя + 1 на заказы
List<User> users = userRepository.findAll();
for (User user : users) {
    System.out.println(user.getOrders().size());
    // Здесь выполняется SELECT по заказам для каждого пользователя
    // Итого: 1 + N запросов
}

// ✅ Хорошо — eager loading
@Query("SELECT u FROM User u LEFT JOIN FETCH u.orders")
List<User> findAllWithOrders();

// Или через EntityGraph
@EntityGraph(attributePaths = {"orders"})
List<User> findAll();

2. Сложные запросы

Для очень сложных запросов SQL может быть проще:

// Native SQL для сложной аналитики
public interface ReportRepository extends JpaRepository<User, Long> {
    @Query(value = 
        "SELECT u.id, COUNT(o.id) as order_count, SUM(o.amount) as total " +
        "FROM users u " +
        "LEFT JOIN orders o ON u.id = o.user_id " +
        "GROUP BY u.id " +
        "HAVING SUM(o.amount) > :minAmount",
        nativeQuery = true
    )
    List<Object[]> getUserStatistics(@Param("minAmount") BigDecimal amount);
}

3. Производительность

ОRM добавляет overhead, но это часто незначительно:

// Profiling
@Service
public class OptimizedUserService {
    @Transactional(readOnly = true)
    public Page<UserDTO> findUsers(Pageable pageable) {
        // readOnly = true → Hibernate не отслеживает изменения
        return userRepository.findAll(pageable)
            .map(this::toDTO);
    }
    
    private UserDTO toDTO(User user) {
        return new UserDTO(
            user.getId(),
            user.getName(),
            user.getEmail()
        );
    }
}

Сравнение подходов

ПодходТипобезопасностьПроизводительностьГибкость
Raw SQL⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
JPQL⭐⭐⭐⭐⭐⭐⭐⭐⭐
Criteria API⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
Spring Data⭐⭐⭐⭐⭐⭐⭐⭐⭐
QueryDSL⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐

Лучшие практики

Используй Spring Data для 90% запросов — просто и достаточно ✅ QueryDSL для типобезопасности в сложных поисках ✅ Native SQL только для аналитики (GROUP BY, window functions) ✅ Проверяй generated SQL через Hibernate logs ✅ Избегай N+1 через FETCH JOIN или EntityGraphКешируй результаты для частых запросов

ORM позволяет забыть о таблицах и работать как с объектами, но необходимо понимать, что происходит под капотом.