Java for Beginner
672 subscribers
541 photos
155 videos
12 files
827 links
Канал от новичков для новичков!
Изучайте Java вместе с нами!
Здесь мы обмениваемся опытом и постоянно изучаем что-то новое!

Наш YouTube канал - https://www.youtube.com/@Java_Beginner-Dev

Наш канал на RUTube - https://rutube.ru/channel/37896292/
Download Telegram
Пагинация, которую начинаешь ненавидеть 😵

Сегодня вновь хочу рассказать о неочевидном поведении 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 -- Сортировка применяется


❗️ Но, для связи @OneToMany или @ManyToOne (department.name) сортировка не работает❗️

Потому что:

Это коллекционная ассоциация
При соединении таблиц может возникать несколько записей для одной основной сущности
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