Вступление
Недавно я столкнулся с такой ситуацией, когда мне нужно было запросить объект с не удаленными дочерними элементами через Spring Data JPA и Hibernate.
Давайте посмотрим на модель предметной области моего тестового приложения:
@Entity public class User { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE) private Long id; @OneToMany(mappedBy = "user", cascade = CascadeType.ALL) private List- items; ... getters, setters, equals, and hashcode. } @Entity public class Item { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE) private Long id; @ManyToOne @JoinColumn(name = "user") private User user; private Boolean deleted; ... getters, setters, equals, and hashcode. }
Вопрос был в следующем: как я могу запросить всех пользователей с не удаленными элементами?
Первая идея состояла в том, чтобы использовать JPQL и @Query
аннотацию внутри данных Spring CrudRepository
. Я написал следующий запрос:
public interface UserRepository extends CrudRepository{ @Query("from User u left join u.items i where i.deleted = false or i.deleted is null") List findUserWithNonDeletedItems(); }
Тестирование
Тестовый код представляет собой:
@SpringBootApplication public class DemoApplication implements CommandLineRunner { @Autowired private UserService userService; @Autowired private UserRepository userRepository; public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } @Override public void run(String... args) throws Exception { userService.createUser(); userService.makeQuery(); } } @Service public class UserService { private final UserRepository userRepository; public UserService(UserRepository userRepository) { this.userRepository = userRepository; } @Transactional public void makeQuery() { var result = userRepository.findUsersWithNonDeletedItems(); assert result.get(0).getItems().size() == 1; } @Transactional public void createUser() { User userWithItems = new User(); var items = List.of( new Item(userWithItems, false), new Item(userWithItems, true) ); userWithItems.setItems(items); userRepository.save(userWithItems); User userWithoutItems = new User(); userRepository.save(userWithoutItems); } }
Когда я запустил этот код, я получил Ошибка утверждения
, потому что поиск пользователей С Не удаленными элементами()
вернул 2 элемента для/| пользователь С элементами , включая удаленный элемент. Причиной такого появления является отсутствие ключевого слова
fetch .
Давайте объясним разницу между левым соединением
и выборка левого соединения
запросы.
Левое соединение
Если мы сделаем следующий запрос JPQL:
from User u left join u.items i where i.deleted = false or i.deleted is null
Hibernate собирается сгенерировать следующую инструкцию SQL:
SELECT u.* FROM user u LEFT OUTER JOIN item i ON i.user_id = u.id WHERE i.deleted = false OR i.deleted is null
Он никогда не запрашивает элементы для каждого пользователя. В результате он делает дополнительный запрос для получения всех элементов пользователя, который не содержит удаленного фильтра.
Выборка левого соединения
Если мы сделаем следующий запрос JPQL:
from User u left join fetch u.items i where i.deleted = false or i.deleted is null
Hibernate собирается сгенерировать следующую инструкцию SQL:
SELECT u.*, i.* FROM user u LEFT OUTER JOIN item i ON i.user_id = u.id WHERE i.deleted = false OR i.deleted is null
В этом запросе hibernate загружает пользователей с их элементами и фильтрует элементы по удаленному столбцу. В результате мы получаем пользователей с удаленными элементами.
Решение
Результирующий запрос выглядит следующим образом:
public interface UserRepository extends CrudRepository{ @Query("from User u left join fetch u.items i where i.deleted = false or i.deleted is null") List findUsersWithNonDeletedItems(); }
Он запрашивает у пользователей только не удаленные элементы.
Оригинал: “https://dev.to/golovpavel/make-a-request-with-sub-condition-for-child-list-via-spring-data-jpa-4inn”