В этом приложении, которое мы разрабатываем, мы заметили, что просмотр был особенно медленным. Я профилировал представление и заметил, что hibernate выполнил один запрос, который занял 10 секунд, даже если в базе данных было всего два объекта для извлечения. Все отношения OneToMany и ManyToMany были ленивыми, так что это не было проблемой. При проверке фактического выполняемого SQL я заметил, что в запросе было более 80 соединений.
При дальнейшем изучении проблемы я заметил, что проблема была вызвана глубокой иерархией OneToOne и ManyToOne отношений между классами сущностей. Итак, я подумал, я просто сделаю их выборку ленивой, это должно решить проблему. Но аннотирование либо @OneToOne(fetch=FetchType.LAZY) либо @ManyToOne(fetch=FetchType.LAZY), похоже, не работает. Либо я получаю исключение, либо тогда они на самом деле не заменяются прокси-объектом и, следовательно, являются ленивыми.
Есть идеи, как я заставлю это работать? Обратите внимание, что я не использую persistence.xml для определения отношений или деталей конфигурации, все делается в коде Java.
Переведено автоматически
Ответ 1
Прежде всего, некоторые пояснения к ответу KLE:
Неограниченная (обнуляемая) взаимно однозначная связь - единственная, которая не может быть проксирована без инструментария байт-кода. Причина этого в том, что объект owner ДОЛЖЕН знать, должно ли свойство association содержать прокси-объект или NULL, и он не может определить это, просмотрев столбцы своей базовой таблицы из-за того, что взаимно однозначное сопоставление обычно выполняется через shared PK, поэтому его в любом случае приходится быстро извлекать, что делает прокси бессмысленным. Вот более подробное объяснение.
ассоциации "многие к одному" (и, очевидно, "один ко многим") не страдают от этой проблемы. Объект-владелец может легко проверить свой собственный FK (и в случае "один ко многим" изначально создается пустой прокси-сервер коллекции и заполняется по запросу), поэтому ассоциация может быть ленивой.
Замена отношения "один к одному" на "один ко многим" в значительной степени никогда не является хорошей идеей. Вы можете заменить его уникальным отношением "многие к одному", но есть другие (возможно, лучшие) варианты.
У Роба Х. есть допустимая точка зрения, однако вы можете быть не в состоянии реализовать ее в зависимости от вашей модели (например, если ваша взаимно однозначная связь обнуляема).
Теперь, что касается первоначального вопроса:
A) @ManyToOne(fetch=FetchType.LAZY) должно работать просто отлично. Вы уверены, что оно не перезаписывается в самом запросе? Можно указать join fetch в HQL и / или явно установить режим выборки через Criteria API, который будет иметь приоритет над аннотацией класса. Если это не так и у вас все еще возникают проблемы, пожалуйста, опубликуйте свои классы, запрос и результирующий SQL для более подробного обсуждения.
B) @OneToOne сложнее. Если оно определенно не обнуляемо, воспользуйтесь предложением Роба Х. и укажите его как таковое:
В противном случае, если вы можете изменить свою базу данных (добавить столбец внешнего ключа в таблицу owner), сделайте это и сопоставьте ее как "присоединенную":
@OneToOne(fetch = FetchType.LAZY) @JoinColumn(name="other_entity_fk") public OtherEntity getOther()
и в Other Entity:
@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 (для выполнения инструментария гибернации во время компиляции):
<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>
<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
Если вы не используете улучшение байт-кода, вы не сможете лениво извлекать родительскую @OneToOne ассоциацию.
Однако чаще всего вам даже не нужна ассоциация на родительской стороне, если вы используете @MapsId на дочерней стороне:
Таким образом, у вас не будет N + 1 проблем с запросами, которые могут быть вызваны mappedBy@OneToOne ассоциацией на родительской стороне.
Ответ 4
Вот что у меня работает (без инструментария):
Вместо использования @OneToOne с обеих сторон, я использую @OneToMany в обратной части отношения (с mappedBy). Это превращает свойство в коллекцию (List в примере ниже), но я перевожу его в элемент в получателе, делая его прозрачным для клиентов.
Эта настройка работает лениво, то есть выбор выполняется только при вызове getPrevious() or getNext() - и только один выбор для каждого вызова.
@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); }