Отношение OneToOne с общим первичным ключом генерирует n + 1 выбор; любой обходной путь? - PullRequest
7 голосов
/ 16 июня 2009

Представьте себе 2 таблицы в реляционной базе данных, например, Персона и биллинг. Между этими объектами определена (необязательная) связь OneToOne, и они совместно используют первичный ключ Person (т. Е. PERSON_ID определен как в Person, так и в Billing, и в последнем это внешний ключ).

При выполнении выбора человека с помощью именованного запроса, например:

from Person p where p.id = :id

Hibernate / JPA генерирует два запроса на выборку, один для таблицы Person, а другой для таблицы Billing.

Приведенный выше пример очень прост и не вызовет проблем с производительностью, поскольку запрос возвращает только один результат. Теперь представьте, что Person имеет n отношения OneToOne (все не обязательно) с другими объектами (все совместно используют первичный ключ Person).

Исправьте меня, если я ошибаюсь, но выполнение запроса select для Person, возвращающего r строк, приведет к тому, что (n+1)*r выборки будут генерироваться Hibernate, даже если ассоциации lazy .

Есть ли обходной путь для этого потенциального сбоя производительности (кроме как вообще не использовать общий первичный ключ)? Спасибо за все ваши идеи.

Ответы [ 5 ]

8 голосов
/ 21 августа 2010

Представьте себе 2 таблицы в реляционной базе данных, например, Персона и биллинг. Между этими объектами определена (необязательная) связь OneToOne,

Ленивая выборка концептуально невозможна для необязательного OneToOne по умолчанию, Hibernate должен обратиться к базе данных, чтобы узнать, является ли связь null или нет. Подробнее на этой старой вики-странице:

Некоторые пояснения по отложенной загрузке (один к одному)

[...]

Теперь рассмотрим наш класс B взаимно-однозначная связь с C

class B {
    private C cee;

    public C getCee() {
        return cee;
    }

    public void setCee(C cee) {
        this.cee = cee;
    }
}

class C {
    // Not important really
}

Сразу после загрузки B вы можете позвонить getCee() чтобы получить C. Но посмотрите, getCee() - это метод ВАШЕГО класса и Hibernate не контролирует его. Hibernate не знает, когда кто-то собирается позвонить getCee(). Тот значит Hibernate должен положить соответствующее значение в "cee" собственность на данный момент загружает B из база данных. Если прокси включен для C, Hibernate может поставить C-прокси объект, который еще не загружен, но будет загружен, когда кто-то его использует. Это дает ленивую нагрузку для one-to-one.

Но теперь представьте, что ваш B объект может или возможно, не связаны C (constrained="false"). Что должно getCee() возврат, когда определено B не C? Ноль. Но помни, Hibernate должен установить правильное значение "cee" в данный момент установлено B (потому что он не знает, когда кто-то позвоню getCee()). Прокси не здесь помощь, потому что сам прокси в уже ненулевой объект.

Итак, резюме: если ваше отображение B-> C является обязательным (constrained=true), Hibernate будет использовать прокси для C приводя к ленивой инициализации. Но если вы разрешите B без C, Hibernate просто нужно проверить наличие C на момент он загружает B. Но ВЫБРАТЬ проверить наличие просто неэффективно потому что тот же SELECT может не просто проверить наличие, но загрузить весь объект. Так что ленивая загрузка уходит .

Итак, невозможно ... по умолчанию.

Есть ли обходной путь для этого потенциального сбоя производительности (кроме как вообще не использовать общий первичный ключ)? Спасибо за все ваши идеи.

Проблема не в общем первичном ключе, с или без общего первичного ключа, вы получите его, проблема в обнуляемом OneToOne.

Первый вариант : использовать инструментарий байт-кода (см. Ссылки на документацию ниже) и без прокси выборка:

@OneToOne( fetch = FetchType.LAZY )
@org.hibernate.annotations.LazyToOne(org.hibernate.annotations.LazyToOneOption.NO_PROXY)

Второй вариант : Использовать подделку ManyToOne(fetch=FetchType.LAZY). Это, наверное, самое простое решение (и, насколько мне известно, рекомендуемое). Но я не проверял это с общим ПК.

Третий вариант : Стремитесь загрузить биллинг, используя join fetch.

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

Ссылки

1 голос
/ 16 июня 2009

Это распространенная проблема с производительностью в Hibernate (просто найдите «Hibernate n + 1»). Есть три варианта, чтобы избежать n + 1 запросов:

  • Размер партии
  • подвыбор
  • ВЕРНУТЬСЯ В ЛИШНИХ запросах

Они описаны в Hibernate FAQs здесь и здесь

0 голосов
/ 17 июня 2009

Эта проблема "n + 1" будет возникать только в том случае, если вы указываете отношение как ленивое или явно указываете, что хотите, чтобы hibernate выполнял отдельный запрос.

Hibernate может извлекать связь с Billing с помощью внешнего объединения для выбора Person, полностью устраняя проблему n + 1. Я думаю, что это указание fetch = "XXX" в ваших файлах hbm.

Выезд Краткий учебник по выбору стратегий

0 голосов
/ 17 июня 2009

Держитесь подальше от отображения OneToOne в hibernate

Это очень сломано и опасно. Вы - одна незначительная ошибка в проблеме с повреждением базы данных.

http://opensource.atlassian.com/projects/hibernate/browse/HHH-2128

0 голосов
/ 16 июня 2009

Вы можете попробовать "оптимизацию вслепую", что хорошо для "n + 1 проблем выбора". Аннотируйте свое поле (или получатель) следующим образом:

@org.hibernate.annotations.BatchSize(size = 10)
java.util.Set<Billing> bills =  new HashSet<Billing>();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...