2015-10-07 6 views
7

У меня есть две таблицы Employee и Департамент следующие являются классы сущностей для них обоихFetchMode Регистрация против подвыбор

Department.java 
@Entity 
@Table(name = "DEPARTMENT") 
public class Department { 
    @Id 
    @Column(name = "DEPARTMENT_ID") 
    @GeneratedValue(strategy = GenerationType.AUTO) 
    private Integer departmentId; 
    @Column(name = "DEPARTMENT_NAME") 
    private String departmentName; 
    @Column(name = "LOCATION") 
    private String location; 

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "department", orphanRemoval = true) 
    @Fetch(FetchMode.SUBSELECT) 
    //@Fetch(FetchMode.JOIN) 
    private List<Employee> employees = new ArrayList<>(); 
} 


Employee.java 
@Entity 
@Table(name = "EMPLOYEE") 
public class Employee { 
    @Id 
    @SequenceGenerator(name = "emp_seq", sequenceName = "seq_employee") 
    @GeneratedValue(generator = "emp_seq") 
    @Column(name = "EMPLOYEE_ID") 
    private Integer employeeId; 
    @Column(name = "EMPLOYEE_NAME") 
    private String employeeName; 

    @ManyToOne 
    @JoinColumn(name = "DEPARTMENT_ID") 
    private Department department; 
} 

Ниже приведены запросы срабатывает, когда я сделал em.find(Department.class, 1);

- поднести режим = fetchmode .join

SELECT department0_.DEPARTMENT_ID AS DEPARTMENT_ID1_0_0_, 
     department0_.DEPARTMENT_NAME AS DEPARTMENT_NAME2_0_0_, 
     department0_.LOCATION   AS LOCATION3_0_0_, 
     employees1_.DEPARTMENT_ID  AS DEPARTMENT_ID3_1_1_, 
     employees1_.EMPLOYEE_ID   AS EMPLOYEE_ID1_1_1_, 
     employees1_.EMPLOYEE_ID   AS EMPLOYEE_ID1_1_2_, 
     employees1_.DEPARTMENT_ID  AS DEPARTMENT_ID3_1_2_, 
     employees1_.EMPLOYEE_NAME  AS EMPLOYEE_NAME2_1_2_ 
    FROM DEPARTMENT department0_ 
    LEFT OUTER JOIN EMPLOYEE employees1_ 
    ON department0_.DEPARTMENT_ID =employees1_.DEPARTMENT_ID 
    WHERE department0_.DEPARTMENT_ID=? 

- режим выборки = fetchmode.subselect

SELECT department0_.DEPARTMENT_ID AS DEPARTMENT_ID1_0_0_, 
     department0_.DEPARTMENT_NAME AS DEPARTMENT_NAME2_0_0_, 
     department0_.LOCATION   AS LOCATION3_0_0_ 
    FROM DEPARTMENT department0_ 
    WHERE department0_.DEPARTMENT_ID=? 

    SELECT employees0_.DEPARTMENT_ID AS DEPARTMENT_ID3_1_0_, 
     employees0_.EMPLOYEE_ID  AS EMPLOYEE_ID1_1_0_, 
     employees0_.EMPLOYEE_ID  AS EMPLOYEE_ID1_1_1_, 
     employees0_.DEPARTMENT_ID  AS DEPARTMENT_ID3_1_1_, 
     employees0_.EMPLOYEE_NAME  AS EMPLOYEE_NAME2_1_1_ 
    FROM EMPLOYEE employees0_ 
    WHERE employees0_.DEPARTMENT_ID=? 

Я просто хотел бы знать, какой из них следует предпочесть FetchMode.JOIN или FetchMode.SUBSELECT? какой из них следует выбрать в каком сценарии?

ответ

7

Я бы сказал, что это зависит ...

Пусть Предположим, у вас есть N сотрудников в отделе, который содержит D байт информации, а средний служащий состоять из Е байт. (Байты представляют собой сумму длины атрибута с некоторыми служебными данными).

Использование команды стратегия, которую вы выполняете 1 запрос и передает данные N * (D + E).

Использование подзапроса стратегия вы выполняете 1 + N запросов, но переносите только данные D + N * E.

Обычно N + 1 запрос является NO GO, если N велико, так что соединение является предпочтительным.

Но на самом деле вы должны проверить свой пробег между количеством запросов и передачей данных.

Обратите внимание, что я не рассматриваю другие аспекты как кэширование Hibernate.

Дополнительный тонкий аспект может быть действительным, если таблица сотрудников является большой и разделена на разделы - разделение разделов на доступ к индексу также приходит к рассмотрению.

20

Стратегия SUBQUERY, на которую ссылается Marmite, связана с FetchMode.SELECT, а не SUBSELECT.

Вывод консоли, который вы опубликовали около fetchmode.subselect Любопытно, потому что это не тот способ, который должен работать.

FetchMode.SUBSELECT

использовать запрос подзапрос, чтобы загрузить дополнительные коллекции

Hibernate docs:

Если один ленивый сбор или однозначная прокси должен быть неправдоподобным, Hibernate загрузит все из них, повторно выполнив исходный запрос в подзапросе. Это работает так же, как и пакетная загрузка, но без поэтапной загрузки.

FetchMode.SUBSELECT должен выглядеть примерно так:

SELECT <employees columns> 
FROM EMPLOYEE employees0_ 
WHERE employees0_.DEPARTMENT_ID IN 
(SELECT department0_.DEPARTMENT_ID FROM DEPARTMENT department0_) 

Вы можете видеть, что этот второй запрос приведет к памяти всех сотрудников, которые принадлежат к какому-то Департаменту (т.е. employee.department_id не null), это не имеет значения, если это не отдел, который вы извлекаете в своем первом запросе. Так что это потенциально серьезная проблема, если таблица сотрудников велика, потому что она может быть accidentially loading a whole database into memory.

Однако FetchMode.SUBSELECT значительно уменьшает количество запросов, поскольку принимает только два запроса в сравнении с N + 1 запросами FecthMode.SELECT.

Возможно, вы думаете, что FetchMode.JOIN делает еще меньше запросов, всего 1, поэтому зачем вообще использовать SUBSELECT? Ну, это правда, но за счет дублированных данных и более тяжелого ответа.

Если однозначная прокси должен быть извлечена с JOIN, запрос может получить:

+---------------+---------+-----------+ 
| DEPARTMENT_ID | BOSS_ID | BOSS_NAME | 
+---------------+---------+-----------+ 
|    1 |  1 | GABRIEL | 
|    2 |  1 | GABRIEL | 
|    3 |  2 | ALEJANDRO | 
+---------------+---------+-----------+ 

Работника данные босса дублируется, если он руководит более чем один отдел, и имеет стоимость в полосой пропускания.

Если ленивая коллекция должна быть извлечена с JOIN, запрос может получить:

+---------------+---------------+-------------+ 
| DEPARTMENT_ID | DEPARTMENT_ID | EMPLOYEE_ID | 
+---------------+---------------+-------------+ 
|    1 | Sales   | GABRIEL  | 
|    1 | Sales   | ALEJANDRO | 
|    2 | RRHH   | DANILO  | 
+---------------+---------------+-------------+ 

Данные отдела дублируется, если он содержит более одного работника (естественный случай). Мы не только страдают от стоимости полосы пропускания, но и получаем дубликат duplicated Department objects, и мы должны использовать SET или DISTINCT_ROOT_ENTITY для удаления дубликатов.

Однако дублирование данных в позиции более низкой задержки является хорошим компромиссом во многих случаях, например, Markus Winand says.

SQL-соединение является еще более эффективным, чем вложенным выбирает подход, даже если он выполняет тот же поиск в индексе, потому что избегает много сети связи. Это еще быстрее, если общая сумма передаваемых данных больше из-за дублирования атрибутов сотрудника для каждой продажи. Это связано с двумя измерениями производительности: временем отклика и пропускной способностью; в компьютерных сетях мы называем их латентностью и пропускной способностью. Полоса пропускания оказывает незначительное влияние на время отклика, но задержки оказывают огромное влияние. Это означает, что количество обращений к базам данных более важно для времени ответа, чем количество переданных данных.

Итак, основная проблема с использованием SUBSELECT заключается в том, что это hard to control и может быть загружен целый график сущностей в память. С помощью пакетной выборки вы получаете связанный объект в отдельном запросе как SUBSELECT (так что вы не получаете дубликатов), постепенно и наиболее важно, чтобы вы запрашивали только связанные объекты (так что вы не страдаете от потенциальной нагрузки огромного графика), поскольку IN в подзапросе фильтруется по идентификаторам, полученным с помощью запроса на выходе).

Hibernate: 
    select ... 
    from mkyong.stock stock0_ 

Hibernate: 
    select ... 
    from mkyong.stock_daily_record stockdaily0_ 
    where 
     stockdaily0_.STOCK_ID in (
      ?, ?, ?, ?, ?, ?, ?, ?, ?, ? 
     ) 

(Это может быть интересным тестом, если Batch выборки с очень высоким размером партии будет действовать как подзапрос, но без выдачи груза всей таблицы)

Пара постов, показывающих различных стратегии Fetching и журналы SQL (очень важно):

Резюме:

  • JOIN: избегает основной вопрос N + 1 запросов, но он может получить данные дублируются.
  • SUBSELECT: избегает N + 1 и не дублирует данные, но загружает все объекты связанного типа в память.

Столы были построены с использованием ascii-tables.

+0

Это грубо вводит в заблуждение. Subselect не будет загружать всю вашу базу данных в память. Связанная статья - это причуда, где subselect игнорирует команды подкачки из родительского элемента, но он все еще является подзапросом. – Planky

+0

В ретроспективе я вижу, что момент, который я делал, был несколько педантичным. Подсчет выборки имеет большую проблему с использованием maxResults, что делает эти два в основном несовместимыми. И случаи, когда это произойдет, были бы совершенно неожиданными и, вероятно, незаметными для производства. – Planky

0

У меня возникла аналогичная проблема у клиента (финансовых услуг), и он хотел «получить данные в одном запросе». Хорошо, я объяснил, что лучше иметь более одного запроса из-за следующего:

Для FetchMode.JOIN отдел будет передан из базы данных в приложение один раз на сотрудника, потому что операция объединения приводит к умножению отдел на одного сотрудника. Если у вас 10 отделов со 100 сотрудниками каждый, каждый из этих 10 отделов будет передан 100 раз в рамках одного запроса, простого SQL. Таким образом, каждый отдел в этом случае переносится в 99 раз чаще, чем необходимо, что приводит к перераспределению данных для отдела.

Для Fetchmode SUBSELECT два запроса запускаются в базу данных. Один из них будет использоваться для получения данных 1000 сотрудников, один из которых получит 10 отделов. Это для меня звучит намного эффективнее. Конечно, вы убедитесь, что индексы установлены, чтобы данные могли быть получены немедленно.

Я бы предпочел FetchMode.SUBSELECT.

Это будет другой случай, если каждый отдел имеет только одного сотрудника, но, как предполагает название «департамент», это вряд ли будет иметь место.

Предлагаю измерить время доступа для поддержки этой теории. Для моего клиента я делал измерения для разных типов доступа, а таблица «department» для моего клиента имела гораздо больше полей (я его не проектировал). Поэтому вскоре стало очевидно, что FetchMode.SUBSELECT был намного быстрее.

0

Planky сказал

(1) Это грубо вводит в заблуждение. (2) Подзаголовок не будет загружать всю вашу базу данных в память. Связанная статья - это причуда, где subselect (3) игнорирует команды подкачки из родительского элемента (4), но он все еще является подзапросом.

  1. После вашего комментария я снова исследовал о FetchMode.SUBSELECT, и я узнал, что мой ответ не совсем правильно.
  2. Это была гипотетическая ситуация, когда гидратация каждой сущности, которая была полностью загружена в память (Employee в этом случае), закончит увлажнение многих других объектов. Настоящая проблема заключается в загрузке всей таблицы, которая была выбрана, если эта таблица содержит тысячи строк (даже если каждый из них не извлекает с нетерпением другие объекты из других таблиц).
  3. Я не знаю, что вы имеете в виду под командами пейджинга от родителя.
  4. Да, это все еще подзаголовок, но я не знаю, что вы пытаетесь указать на это.

Выход консоли, что вы писали об fetchmode.subselect любопытно, потому что это не тот путь, который должен работать.

Это верно, но только тогда, когда есть больше, чем лицо отдела hidrated (что означает больше, чем одна коллекции сотрудников неинициализированный), я проверил его с 3.6.10.Final и 4.3.8.Final В сценариях 2.2 (FetchMode.SUBSELECT hidrating 2 of 3 Departments) и 3.2 (FetchMode.SUBSELECT hidrating all Departments), SubselectFetch.toSubselectString возвращает следующее (ссылки на Hibernate классы взяты из 4.3.8.Final тега):

select this_.DEPARTMENT_ID from SUBSELECT_DEPARTMENT this_ 

Этот подзапрос после используется для построения ИНЕКЕ по OneToManyJoinWalker.initStatementString заканчивающегося

employees0_.DEPARTMENT_ID in (select this_.DEPARTMENT_ID from SUBSELECT_DEPARTMENT this_) 

Тогда где положение добавляется в CollectionJoinWalker.whereString заканчивая

select employees0_.DEPARTMENT_ID as DEPARTMENT3_2_1_, employees0_.EMPLOYEE_ID as EMPLOYEE1_1_, employees0_.EMPLOYEE_ID as EMPLOYEE1_3_0_, employees0_.DEPARTMENT_ID as DEPARTMENT3_3_0_, employees0_.EMPLOYEE_NAME as EMPLOYEE2_3_0_ from SUBSELECT_EMPLOYEE employees0_ where employees0_.DEPARTMENT_ID in (select this_.DEPARTMENT_ID from SUBSELECT_DEPARTMENT this_) 

Whit этого запроса, в обоих случаях все Служащие быть извлечено и увлажненной. Это явно проблема в сценарии 2.2, потому что мы увлажняем только отделы 1 и 2, но также увлажняем всех сотрудников, даже если они не принадлежат к этим департаментам (в данном случае сотрудники Департамента 3).

Если в сеансе есть только один элемент подразделения, который не был инициализирован, то его запрос будет похож на тот, который написал eatSleepCode. Проверить scenario 1.2

select subselectd0_.department_id as departme1_2_0_, subselectd0_.department_name as departme2_2_0_, subselectd0_.location as location3_2_0_ from subselect_department subselectd0_ where subselectd0_.department_id=? 

От FetchStyle

/** 
    * Performs a separate SQL select to load the indicated data. This can either be eager (the second select is 
    * issued immediately) or lazy (the second select is delayed until the data is needed). 
    */ 
    SELECT, 
    /** 
    * Inherently an eager style of fetching. The data to be fetched is obtained as part of an SQL join. 
    */ 
    JOIN, 
    /** 
    * Initializes a number of indicated data items (entities or collections) in a series of grouped sql selects 
    * using an in-style sql restriction to define the batch size. Again, can be either eager or lazy. 
    */ 
    BATCH, 
    /** 
    * Performs fetching of associated data (currently limited to only collections) based on the sql restriction 
    * used to load the owner. Again, can be either eager or lazy. 
    */ 
    SUBSELECT 

До сих пор я не мог решить, что значит это Javadoc с:

на основе ограничения SQL, используемого для загрузки владельца

UPDATE Planky сказал:

Вместо этого он просто будет загрузить таблицу в худшем случае, и даже тогда, только если ваш первоначальный запрос не имел, где положение.Поэтому я бы сказал, что с использованием запросов подзапроса может неожиданно загрузить всю таблицу, если вы ОГРАНИЧИТЕ результаты, и у вас нет критериев WHERE.

Это верно, и это очень важная деталь, которую я испытано в новой scenario 4.2

запросе сгенерированного для выборки работников

select employees0_.department_id as departme3_4_1_, employees0_.employee_id as employee1_5_1_, employees0_.employee_id as employee1_5_0_, employees0_.department_id as departme3_5_0_, employees0_.employee_name as employee2_5_0_ from subselect_employee employees0_ where employees0_.department_id in (select this_.department_id from subselect_department this_ where this_.department_name>=?) 

подзапрос внутри где положение содержит исходное ограничение this_.department_name> =?, избегая загрузки всех сотрудников. Это то, что означает, что Javadoc с

на основе ограничения SQL, используемого для загрузки владельцу

Все, что я сказал о FetchMode.JOIN и различиях с FetchMode.SUBSELECT остается верным (а также применяется для FetchMode.SELECT).

+0

Спасибо, что нашли время ответить. Я полагаю, что я гиперболизировал, когда я сказал, что это было просто ошибочным. Что я имел в виду, когда я сказал: «Связанная статья о причуде, где subselect игнорирует команды подкачки от родителя», заключается в том, что он описывает проблему при использовании конструкции _limit_ sql, которая обычно используется для результатов подкачки. – Planky

+0

Мое мнение состояло в том, что проблема не в том, что загрузите всю базу данных (загружая все ассоциации - проблема, которая может возникнуть с Hibernate при плохой настройке). Вместо этого он просто загрузит таблицу в худшем случае, и даже тогда, только если ваш первоначальный запрос не имеет предложения where. Поэтому я бы сказал, что использование запросов подзапроса может неожиданно загрузить всю таблицу, если вы ОГРАНИЧИТЕ результаты _and_, у вас нет критериев WHERE. – Planky

Смежные вопросы