Пагинация, которую начинаешь ненавидеть 😵
Сегодня вновь хочу рассказать о неочевидном поведении Spring Data JPA, которое хоть и встречается нечасто, но может попортить нервы.
Давайте представим, что есть связанные сущности(код условный, никакого реального совпадения):
И есть бизнес-задача: Провести сортировку (SortBy в Pageable) по полям вложенных сущностей (т.е. по workstation.number и department.name).
🙅♂️ Как хочется сделать
Как минимум все по стандарту:
И выглядит вроде бы все пристойно.
Но! Как всегда в магии Spring'а есть НО✌️
Для связи @OneToOne (Workstation.number) сортировка работает, потому что это прямое простое соединение (join), и JPA может легко построить корректный SQL-запрос:
❗️ Но, для связи @OneToMany или @ManyToOne (department.name) сортировка не работает❗️
Потому что:
Это коллекционная ассоциация
При соединении таблиц может возникать несколько записей для одной основной сущности
JPA/Hibernate не может автоматически разрешить, как именно нужно применять сортировку в таком случае и не делает НИЧЕГО🙄
🆗 Как надо сделать
Явный JOIN + ORDER BY в JPQL
Явное указание, как соединять таблицы и ORDER BY до пагинации как минимум решат все проблемы🏝
Использование JOIN FETCH (если нужны данные сразу)
Тот же вариант что и выше, но если не жалко памяти🆘
Specification API
Вариант для любителей пожесче🧑💻
Как не сойти с ума при поиске подобных ошибок?🤪
Всегда, на стадии разработки и тестирования включайте SQL в логах (spring.jpa.show-sql=true) и будет Вам счастие.
Помните - если сортировка не применяется – валите всё на JOIN.
Понравился стиль подачи материала?
Отправь другу и ставь - 🔥
Если бывало такое -❤️
#Java #join #autor
Сегодня вновь хочу рассказать о неочевидном поведении Spring Data JPA, которое хоть и встречается нечасто, но может попортить нервы.
Давайте представим, что есть связанные сущности(код условный, никакого реального совпадения):
@Entity
@Data
public class Employee {
@Id
private Long id;
private String name;
@OneToOne
@JoinColumn(name = "workstation_id")
private Workstation workstation;
@ManyToOne
@JoinColumn(name = "department_id")
private Department department;
}
@Entity
@Data
public class Workstation {
@Id
private Long id;
private String number; // Поле для сортировки
}
@Entity
@Data
public class Department {
@Id
private Long id;
private String name; // Поле для сортировки
}
И есть бизнес-задача: Провести сортировку (SortBy в Pageable) по полям вложенных сущностей (т.е. по workstation.number и department.name).
Как минимум все по стандарту:
@Query("SELECT e FROM Employee e WHERE e.name = :name")
Page<Employee> findByName(@Param("name") String name, Pageable pageable)
Естественно поля могут передаваться таким образом:
Pageable pageable = PageRequest.of(0, 10, Sort.by(Sort.Direction.fromString("workstation.number"));
или
Pageable pageable = PageRequest.of(0, 10, Sort.by(Sort.Direction.fromString("department.name"));
И выглядит вроде бы все пристойно.
Но! Как всегда в магии Spring'а есть НО
Для связи @OneToOne (Workstation.number) сортировка работает, потому что это прямое простое соединение (join), и JPA может легко построить корректный SQL-запрос:
SELECT e.* FROM employee e
JOIN workstation w ON e.workstation_id = w.id
WHERE e.name = 'John'
ORDER BY w.number ASC -- Сортировка применяется
Потому что:
Это коллекционная ассоциация
При соединении таблиц может возникать несколько записей для одной основной сущности
JPA/Hibernate не может автоматически разрешить, как именно нужно применять сортировку в таком случае и не делает НИЧЕГО
Явный JOIN + ORDER BY в JPQL
@Query("SELECT e FROM Employee e " +
"LEFT JOIN e.department d " + // Явное соединение
"WHERE e.name = :name " +
"ORDER BY d.name") // Сортировка в запросе(но не обязательно)
Page<Employee> findByNameWithDepartmentSort(
@Param("name") String name,
Pageable pageable);
Явное указание, как соединять таблицы и ORDER BY до пагинации как минимум решат все проблемы
Использование JOIN FETCH (если нужны данные сразу)
@Query("SELECT DISTINCT e FROM Employee e " +
"LEFT JOIN FETCH e.department d " + // Загружаем department сразу
"WHERE e.name = :name " +
"ORDER BY d.name")
Page<Employee> findByNameWithDepartmentFetch(
@Param("name") String name,
Pageable pageable);
Тот же вариант что и выше, но если не жалко памяти
Specification API
public interface EmployeeRepository extends
JpaRepository<Employee, Long>,
JpaSpecificationExecutor<Employee> { }
// В сервисе:
Specification<Employee> spec = (root, query, cb) -> {
Join<Employee, Department> department = root.join("department");
return cb.equal(root.get("name"), name);
};
Sort sort = Sort.by("department.name").ascending();
Pageable pageable = PageRequest.of(0, 10, sort);
Page<Employee> result = repository.findAll(spec, pageable);
Вариант для любителей пожесче
Как не сойти с ума при поиске подобных ошибок?
Всегда, на стадии разработки и тестирования включайте SQL в логах (spring.jpa.show-sql=true) и будет Вам счастие.
Помните - если сортировка не применяется – валите всё на JOIN.
Понравился стиль подачи материала?
Отправь другу и ставь - 🔥
Если бывало такое -
#Java #join #autor
Please open Telegram to view this post
VIEW IN TELEGRAM