Как избежать присоединения "взрывов" в JPA с Hibernate, когда необходимы поля из нескольких таблиц? - PullRequest
1 голос
/ 27 мая 2020

Допустим, у меня есть следующий метод в JpaRepository:

@EntityGraph(value = "Subject.allJoins", type = EntityGraphType.FETCH)
    @Query("select s from Subject s" + FIND_QUERY_WHERE)
    Page<Subject> findInProject(@Param("projectId") UUID projectId, <additional params>

Как видите, я уже использую EntityGraph с нужными мне соединениями. Результирующий запрос SQL, который генерирует Hibernate, выглядит следующим образом (где в основном опущены)

select
    subject0_.id,
    <all kinds of fields including some duplicates>
from
    subject subject0_
    left outer join project project1_ on subject0_.project_id = project1_.id
    left outer join subject_property_value properties2_ on subject0_.id = properties2_.subject_id
    left outer join property_value propertyva3_ on properties2_.property_value_id = propertyva3_.id
    left outer join ingestion_id_mapping ingestedme4_ on subject0_.id = ingestedme4_.subject_id
where
    subject0_.project_id = '123'
order by
    subject0_.name asc

Из-за всех соединений здесь, которые умножают результат на количество строк в результате соединения, результирующий набор разбивается на сотни тысяч строк, даже если общее количество объектов составляет всего несколько сотен.

Обратите внимание, что я буду делать проекцию, и это уже позволит избежать выбора некоторых полей, но объединения все еще необходимы.

Что я могу сделать, чтобы оптимизировать это?

Примечание. Мне действительно нужны все данные для немедленной сериализации для клиента, поэтому просто оставьте его в Hibernate, получив модель entity и использование методов Getter для каждой ассоциации занимает намного больше времени, чем это.

Моя текущая идея состоит в том, что я должен выполнять запрос несколько раз с одним и тем же where для каждого отдельного соединения, а затем объединять результаты в единый объект. Это не конец света, если я прочитаю больше или меньше строк в последующем запросе из-за добавленных или удаленных строк в исходной таблице, потому что я могу просто взять наименьшее подмножество идентификаторов субъектов и получить из этого результат. 1016 * Но есть ли что-нибудь умнее и / или проще, чем это?

Ответы [ 3 ]

1 голос
/ 27 мая 2020

Я возьму пример футбольного клуба, у которого есть страна, стадион и список игроков.

Ваш первый запрос должен использоваться только для фильтрации строк, которые вы хотите получить из базы данных. В этом случае вы также можете получить отношения 1: 1, но не отношения 1: n. Итак, в моем примере 1-й запрос должен:

  • фильтровать все клубы, соответствующие критериям
  • получать все отношения 1: 1 (страна и стадион для каждого клуба).

Затем вы можете сделать отдельный список для каждого подсписка. Тем не менее в моем примере вы должны выбрать всех игроков, клуб которых находится в списке, который вы предоставляете в качестве параметра запроса (являющегося результатом вашего первого запроса). Запрос будет примерно таким:

 String jpql = "select p from Player p where p.club in :clubs";

При этом вы также можете предоставить entityGraph для загрузки атрибутов игроков. Это хорошо работает, когда вы выполняете разбиение на страницы (результат 1-го запроса не будет иметь большого значения).

Этот способ хорошо описан Владом Михалча: Лучший способ исправить Hibernate MultipleBagFetchException

Очень советую взглянуть.

1 голос
/ 28 мая 2020

Это идеальный вариант использования Blaze-Persistence Entity Views .

Я создал библиотеку, чтобы обеспечить простое сопоставление между моделями JPA и пользовательским интерфейсом или моделями, определяемыми абстрактным классом, что-то вроде Прогнозы весенних данных на стероидах. Идея состоит в том, что вы определяете свою целевую структуру (модель домена) так, как вам нравится, и сопоставляете атрибуты (геттеры) через выражения JPQL с моделью сущности. Поскольку имя атрибута используется в качестве сопоставления по умолчанию, в большинстве случаев явные сопоставления не требуются, поскольку в 80% случаев использования DTO являются подмножеством модели сущности.

Интересная часть заключается в том, что вы можете указать стратегию выборки, которую следует использовать. Образец модели может выглядеть следующим образом:

@EntityView(Subject.class)
public interface SubjectView {
    @IdMapping
    Integer getId();
    ProjectView getProject();
    @Mapping(fetch = SUBSELECT)
    Set<PropertyValueView> getProperties();
    Set<IngestionMappingView> getMappings();
}
@EntityView(Project.class)
public interface ProjectView {
    @IdMapping
    Integer getId();
    String getName();
}
@EntityView(PropertyValue.class)
public interface PropertyValueView {
    @IdMapping
    Integer getId();
    String getName();
}
@EntityView(IngestionMapping.class)
public interface IngestionMappingView {
    @IdMapping
    Integer getId();
    String getName();
}

Запрос - это вопрос применения представления сущности к запросу, простейший из которых - это просто запрос по идентификатору.

SubjectView p = entityViewManager.find(entityManager, SubjectView.class, id);

Интеграция Spring Data позволяет использовать ее почти как Spring Data Projection: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring -data-features т.е. иметь репозиторий, подобный следующему

@Repository
public interface SubjectRepository {
    Page<SubjectView> findByProjectId(@Param("projectId") UUID projectId, <additional params>);
}

Вы можете узнать больше о поддерживаемых стратегиях выборки в документации entity-view , и я обычно рекомендую вам использовать стратегию выборки MULTISET, если это возможно, поскольку она обычно обеспечивает лучшую производительность.

1 голос
/ 27 мая 2020

Проблема в том, что fetch-join выполняет подвыбор для каждой связанной сущности / таблицы. Вместо этого вы должны объединять объекты только с отношением 1: 1. Затем выбираются другие объекты при первом доступе к ним. В результате получается одна строка для каждой темы и одна выборка с n строками для каждой сущности, не входящей в начальный выбор.

Если частичные выборки занимают слишком много времени, попробуйте добавить сущности с наименьшим количеством записей в выборку .

...