Подтвердить что ты не робот

Создание отношения OneToOne к ленивому

В этом приложении мы разрабатываем, мы заметили, что представление было особенно медленным. Я профилировал представление и заметил, что был один запрос, выполненный спящим ходом, который занял 10 секунд, даже если в базу данных были только два объекта. Все отношения OneToMany и ManyToMany были ленивыми, так что это не проблема. При проверке фактического выполнения SQL я заметил, что в запросе было более 80 объединений.

Дальнейшая проверка проблемы, я заметил, что проблема была вызвана глубокой иерархией отношений OneToOne и ManyToOne между классами сущностей. Итак, подумал я, я просто сделаю их ленивыми, что должно решить проблему. Но аннотация для @OneToOne(fetch=FetchType.LAZY) или @ManyToOne(fetch=FetchType.LAZY) не работает. Либо я получаю исключение, либо они фактически не заменяются прокси-объектом и, следовательно, ленивы.

Любые идеи, как я получу это для работы? Обратите внимание, что я не использую persistence.xml для определения отношений или деталей конфигурации, все делается в Java-коде.

4b9b3361

Ответ 1

Прежде всего, некоторые пояснения к ответу KLE:

  1. Неограниченная (обнуляемая) взаимно-однозначная связь является единственной, которую нельзя проксировать без инструментария байт-кода. Причина этого заключается в том, что объект-владелец ДОЛЖЕН знать, должно ли свойство ассоциации содержать прокси-объект или NULL, и он не может определить это, просматривая столбцы своей базовой таблицы из-за того, что один-к-одному обычно сопоставляют через общий PK, поэтому в любом случае должен быть извлечен с нетерпением, делая прокси бессмысленным. Здесь более подробное объяснение.

  2. ассоциации многие-к-одному (и, очевидно, один-ко-многим) не страдают от этой проблемы. Субъект-владелец может легко проверить свой собственный FK (и в случае "один ко многим" пустой прокси-сервер коллекции создается изначально и заполняется по требованию), поэтому сопоставление может быть ленивым.

  3. Замена один-к-одному на один-ко-многим никогда не бывает хорошей идеей. Вы можете заменить его уникальным много-к-одному, но есть и другие (возможно, лучшие) варианты.

У Роба Х. есть действительная точка зрения, однако вы не сможете реализовать ее в зависимости от вашей модели (например, если ваша однозначная связь обнуляется).

Теперь, что касается оригинального вопроса:

A) @ManyToOne(fetch=FetchType.LAZY) должен работать просто отлично. Вы уверены, что он не был перезаписан в самом запросе? Можно указать join fetch в HQL и/или явно установить режим выборки через Criteria API, который будет иметь приоритет над аннотацией класса. Если это не так, и у вас все еще есть проблемы, пожалуйста, опубликуйте ваши классы, запрос и полученный SQL для более подробного обсуждения.

Б) @OneToOne сложнее. Если это определенно не обнуляемо, воспользуйтесь предложением Роба Х. и укажите его так:

@OneToOne(optional = false, fetch = FetchType.LAZY)

В противном случае, если вы можете изменить свою базу данных (добавить столбец внешнего ключа в таблицу владельцев), сделайте это и сопоставьте ее как "присоединенную":

@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name="other_entity_fk")
public OtherEntity getOther()

и в OtherEntity:

@OneToOne(mappedBy = "other")
public OwnerEntity getOwner()

Если вы не можете этого сделать (и не можете жить с нетерпеливым извлечением), байт-код является единственным вариантом. Я должен согласиться с CPerkins, однако - если у вас есть 80 !!! присоединяется из-за активных ассоциаций OneToOne, у вас есть большие проблемы, чем это :-)

Ответ 2

Чтобы получить ленивую загрузку, работая над сопоставлениями "один-к-одному" с нулевым значением, вам нужно позволить hibernate выполнить компиляцию времени и добавить @LazyToOne(value = LazyToOneOption.NO_PROXY) к взаимно однозначному отношению.

Пример отображения:

@OneToOne(fetch = FetchType.LAZY)  
@JoinColumn(name="other_entity_fk")
@LazyToOne(value = LazyToOneOption.NO_PROXY)
public OtherEntity getOther()

Пример Ant Расширение файла сборки (для выполнения инструментария времени компиляции Hibernate):

<property name="src" value="/your/src/directory"/><!-- path of the source files --> 
<property name="libs" value="/your/libs/directory"/><!-- path of your libraries --> 
<property name="destination" value="/your/build/directory"/><!-- path of your build directory --> 

<fileset id="applibs" dir="${libs}"> 
  <include name="hibernate3.jar" /> 
  <!-- include any other libraries you'll need here --> 
</fileset> 

<target name="compile"> 
  <javac srcdir="${src}" destdir="${destination}" debug="yes"> 
    <classpath> 
      <fileset refid="applibs"/> 
    </classpath> 
  </javac> 
</target> 

<target name="instrument" depends="compile"> 
  <taskdef name="instrument" classname="org.hibernate.tool.instrument.javassist.InstrumentTask"> 
    <classpath> 
      <fileset refid="applibs"/> 
    </classpath> 
  </taskdef> 

  <instrument verbose="true"> 
    <fileset dir="${destination}"> 
      <!-- substitute the package where you keep your domain objs --> 
      <include name="/com/mycompany/domainobjects/*.class"/> 
    </fileset> 
  </instrument> 
</target>

Ответ 3

Основная идея, связанная с XToOnes в Hibernate, заключается в том, что они не ленивы в большинстве случаев.

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

Отредактировано: для получения дополнительной информации см. ответ ChssPly76. Этот менее точный и подробный, он не имеет ничего предложить. Спасибо ChssPly76.

Ответ 4

Здесь что-то, что работает для меня (без инструментов):

Вместо использования @OneToOne с обеих сторон, я использую @OneToMany в обратной части отношения (с mappedBy). Это делает свойство коллекцией (List в примере ниже), но я переводю его в элемент в getter, делая его прозрачным для клиентов.

Эта установка работает лениво, т.е. выбор производится только тогда, когда вызываются getPrevious() или getNext() - и только один для каждого вызова.

Структура таблицы:

CREATE TABLE `TB_ISSUE` (
    `ID`            INT(9) NOT NULL AUTO_INCREMENT,
    `NAME`          VARCHAR(255) NULL,
    `PREVIOUS`      DECIMAL(9,2) NULL
    CONSTRAINT `PK_ISSUE` PRIMARY KEY (`ID`)
);
ALTER TABLE `TB_ISSUE` ADD CONSTRAINT `FK_ISSUE_ISSUE_PREVIOUS`
                 FOREIGN KEY (`PREVIOUS`) REFERENCES `TB_ISSUE` (`ID`);

Класс:

@Entity
@Table(name = "TB_ISSUE") 
public class Issue {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    protected Integer id;

    @Column
    private String name;

    @OneToOne(fetch=FetchType.LAZY)  // one to one, as expected
    @JoinColumn(name="previous")
    private Issue previous;

    // use @OneToMany instead of @OneToOne to "fake" the lazy loading
    @OneToMany(mappedBy="previous", fetch=FetchType.LAZY)
    // notice the type isnt Issue, but a collection (that will have 0 or 1 items)
    private List<Issue> next;

    public Integer getId() { return id; }
    public String getName() { return name; }

    public Issue getPrevious() { return previous; }
    // in the getter, transform the collection into an Issue for the clients
    public Issue getNext() { return next.isEmpty() ? null : next.get(0); }

}

Ответ 5

В нативных XML-сопоставлениях Hibernate этого можно добиться, объявив сопоставление " один к одному" с ограниченным атрибутом, установленным в значение "истина". Я не уверен, что такое эквивалент аннотации Hibernate/JPA, и быстрый поиск документа не дал ответа, но, надеюсь, это даст вам возможность продолжить.

Ответ 6

Как уже прекрасно объяснено ChssPly76, прокси-серверы Hibernate не помогают с неограниченными (обнуляемыми) ассоциациями "один-к-одному", НО есть трюк, объясненный здесь, чтобы избежать установки приборов. Идея состоит в том, чтобы обмануть Hibernate, что класс сущности, который мы хотим использовать, уже был инструментализован: вы обрабатываете его вручную в исходном коде. Это легко! Я реализовал его с помощью CGLib в качестве поставщика байт-кода, и он работает (убедитесь, что вы настроили lazy = "no-proxy" и fetch = "select", а не "join", в вашем HBM).

Я думаю, что это хорошая альтернатива реальному (я имею в виду автоматическое) инструментарию, когда у вас есть только одно взаимно однозначное отношение, которое вы хотите сделать ленивым. Главный недостаток заключается в том, что решение зависит от используемого провайдера байт-кода, поэтому прокомментируйте свой класс, потому что в будущем вам может понадобиться изменить поставщика байт-кода; конечно, вы также модифицируете свою модель bean по технической причине, и это не нормально.