Spring Data JPA запускает дополнительные запросы select при использовании entity graph и отображаемого объекта нет (null)

#java #spring #jpa #entity #entitygraph

#java #spring #jpa #сущность #entitygraph

Вопрос:

У меня есть 2 объекта в соотношении один к одному (это может быть много к одному, но в данном случае я не думаю, что это имеет большое значение), например:

 @Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String email;
 
    //...
}
@Entity
public class Post {
 
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String subject;
 
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn
    private User otherUser;
     
    //...
}

The repository looks like this
@EntityGraph(attributePaths = {"otherUser"})
Iterable<Post> findAll(Predicate predicate);
  

Поэтому я переопределяю findAll метод, который они предоставляют по умолчанию, потому что я все еще хочу использовать предикат, и я все еще хочу присоединить запрос к другой таблице. Это работает почти идеально. Созданный запрос выглядит следующим образом

 select
post0_.id as id1_4_0_,
user0_.id as id1_0_1_,
post0_.subject as subject2_4_0_,
user0_.name as name2_0_1_,
user0_.email as email3_0_1_
from
    Post post0_
    left outer join User user0_ on post0_.id = user0_.id
where
   ***contents of Predicate****
  

Теперь проблема в том, что если пользователь возвращает в основном null (все атрибуты user равны null, что означает, что один не существует, чтобы соответствовать условию) из этого запроса, тогда Spring создает дополнительные запросы select непосредственно к таблице User, ищущей пользователя. Например, предположим, что есть сообщение с id = 4. Если нет пользователя с id = 4, Spring создаст и запустит обычный запрос, подобный такому:

 select
user0_.id as id1_0_1_,
user0_.name as name2_0_1_,
user0_.email as email3_0_1_
from
    User user0_ 
    left outer join User user0_ on post0_.id = user0_.id
where
   user0_id = 4
  

Это проблема, если набор данных большой и содержит большое количество записей, которые не соответствуют исходному запросу join, тогда будет создано и выполнено слишком много дополнительных запросов. Если он не соответствует критериям объединения, я бы хотел, чтобы эта запись объекта игнорировалась. Используя пример, я бы хотел, чтобы любой идентификатор пользователя, который не соответствует идентификатору post, игнорировался, а не другой оператор select.

Есть ли способ предотвратить создание этих дополнительных запросов select?

Комментарии:

1. Я вижу такое же поведение, в основном JPA правильно выполняет запрос на объединение слева, но когда object равен null (данные отсутствуют), он выполнит другой запрос для объекта (всегда возвращающий null). Не стесняйтесь также связаться со мной, если вам нужны дополнительные сведения.

2. Любопытное поведение. Выполняется ли запрос 1 к пользователю, когда вы фактически разыменовываете пользователя, или это происходит сразу после первоначального запроса find all?

3. @Atmasв моих тестах это происходит немедленно (вам не нужно ссылаться)

4. Пока не видел объяснения этому поведению, но, изучая ваш запрос подробнее, я также хочу спросить, почему ваши запросы объединяются post.id для user.id. Я думаю, что может возникнуть проблема с вашим отображением, которая приводит к запутанным результатам. Даже второй отправленный вами запрос пытается сделать то же самое…. Возможно, придется добавить mappedBy или что-то еще, чтобы прояснить взаимосвязь.

5. Также я где-то читал, что поведение entity graph заключается в том, чтобы сообщать JPA, какой результирующий набор гарантирует проекцию, но НЕ как его извлекать, поэтому, если в вашем коде есть способ либо в JPQL, либо в критериях ТАКЖЕ указать синтаксис объединения выборки, то это также может сработать.

Ответ №1:

Предполагая, что вы используете Hibernate в своих операторах log, я полагаю, что вы столкнулись с ситуацией загрузки n 1 sql и вам нужно добавить другие / дополнительные директивы выборки, чтобы otherUser ассоциация быстро извлекалась, поэтому для нее не требуется последующая инструкция select.

Самая авторитетная документация, которую я могу найти по этой теме, находится здесь: https://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#fetching

Я бы посоветовал сначала изменить ваш FetchType.LAZY на FetchType.EAGER . Если размещение аннотации JPA FetchType.EAGER в otherUser переменной-члене не решает вашу проблему, рассмотрите возможность использования @Fetch(FetchMode.JOIN) для ее дальнейшего усиления.

Кроме того, для полноты картины, если вы решите использовать метод на основе запросов для извлечения (например, JPQL), вы можете включить директиву FETCH в свой JPQL для достижения эффекта по мере необходимости.