Сделать onetoone-отношение ленивым
в этом приложении, которое мы разрабатываем, мы заметили, что вид был особенно медленным. Я профилировал представление и заметил, что был один запрос, выполненный hibernate, который занял 10 секунд, даже если в базе данных было только два объекта для извлечения. Все!--0--> и ManyToMany
отношения были ленивыми, так что это не было проблемой. При проверке фактического выполнения SQL я заметил, что в запросе было более 80 соединений.
дальнейшее изучение вопроса, я заметил, что проблема была вызвана глубокой иерархией OneToOne
и ManyToOne
отношения между классами сущностей. Поэтому, подумал я, я просто сделаю их ленивыми, это должно решить проблему. Но аннотирование либо @OneToOne(fetch=FetchType.LAZY)
или @ManyToOne(fetch=FetchType.LAZY)
не работает. Либо я получаю исключение, либо они фактически не заменяются прокси-объектом и, таким образом, ленивы.
есть идеи, как я заставлю это работать? Обратите внимание, что я не использую persistence.xml
определить отношения или детали конфигурации, все делается в коде java.
6 ответов
во-первых, некоторые разъяснения KLE'ы ответ:
Unconstrained (nullable) one-to-one association является единственным, который не может быть проксирован без инструментария байт-кода. Причина этого заключается в том, что сущность владельца должна знать, должно ли свойство ассоциации содержать прокси-объект или NULL, и она не может определить это, глядя на столбцы своей базовой таблицы из-за того, что один к одному обычно сопоставляется через общий ПК, поэтому он должен быть нетерпеливо извлечена равно прокси-бессмысленно. Вот более подробно объяснение.
многие-к-одному (и один-ко-многим, очевидно) не страдают от этой проблемы. Владелец объекта может легко проверить свой собственный FK (и в случае одного ко многим пустой прокси-сервер коллекции создается изначально и заполняется по требованию), поэтому ассоциация может быть ленивой.
замена один-к-одному один-ко-многим почти никогда не является хорошей идеей. Вы можете замените его уникальным много-к-одному, но есть и другие (возможно, лучшие) варианты.
Роб Х. имеет допустимую точку, однако вы не можете реализовать ее в зависимости от вашей модели (например, если ваша ассоциация один к одному is nullable).
теперь, что касается первоначального вопроса:
A)@ManyToOne(fetch=FetchType.LAZY)
должно работать нормально. Вы уверены, что он не перезаписывается в самом запросе? Можно указать join fetch
в HQL и / или явно установите режим выборки через API критериев, который будет иметь приоритет над аннотацией класса. Если это не так, и у вас все еще есть проблемы, разместите свои классы, запрос и полученный SQL для более предметного разговора.
B)@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, у вас есть большие проблемы, то это : -)
чтобы получить ленивую загрузку, работающую над nullable один к одному сопоставлениями, вам нужно позволить 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>
основная идея ухоженный XToOnes в Hibernate является то, что они не ленятся в большинстве случаев.
одна из причин заключается в том, что, когда Hibernate должен решить поставить прокси (с идентификатором) или null,
он должен смотреть в другую таблицу в любом случае присоединиться. Стоимость доступа к другой таблице в базе данных значительна, поэтому она может также получить данные для этой таблицы в данный момент( не ленивое поведение), вместо того, чтобы получать это в более позднем запросе, который потребует второй доступ к той же таблице.
отредактировано: для получения дополнительной информации см. ответ ChssPly76. Этот менее точный и подробный, он ничего не может предложить. Спасибо ChssPly76.
вот что работает для меня (без инструментов):
вместо @OneToOne
С обеих сторон, я использую @OneToMany
в обратной части отношения (с mappedBy
). Это делает свойство коллекцией (List
в приведенном ниже примере), но я перевожу его в элемент в геттере, делая его прозрачным для клиентов.
Эта настройка работает лениво, то есть выбор производится только тогда, когда 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); }
}
в собственных XML-сопоставлениях Hibernate вы можете выполнить это, объявив один на один сопоставление с ограниченного атрибут имеет значение true. Я не уверен, что такое эквивалент аннотации Hibernate/JPA, и быстрый поиск документа не дал ответа, но, надеюсь, это даст вам зацепку.
Как уже прекрасно объяснил ChssPly76, прокси Hibernate не помогают с неограниченными (nullable) один к одному ассоциациями, но есть трюк, объясненный здесь чтобы избежать настройка приборов. Идея в том, чтобы обмануть гибернации, что класс сущностей, который мы хотим использовать уже инструментированных: вы прибор вручную в исходном коде. Это просто! Я реализовал его с CGLib в качестве поставщика байт-кода, и он работает (убедитесь, что вы настроили lazy= "no-proxy"и fetch=" select", а не" join", в вашем HBM).
Я думаю, что это хорошая альтернатива реальные (Я имею в виду автоматическое) инструментирование, когда у вас есть только одно отношение один к одному, которое вы хотите сделать ленивым. Основным недостатком является то, что решение зависит от используемого поставщика байт-кода, поэтому комментируйте свой класс точно, потому что в будущем вам может потребоваться изменить поставщик байт-кода; конечно, вы также изменяете свой компонент модели для техническая причина и это не нормально.