Вопрос-ответ

How can I make a JPA OneToOne relation lazy

Как я могу сделать отношение JPA OneToOne ленивым

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

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

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

Переведено автоматически
Ответ 1

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


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



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



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



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

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

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

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

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

В противном случае, если вы можете изменить свою базу данных (добавить столбец внешнего ключа в таблицу 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>

<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

Если вы не используете улучшение байт-кода, вы не сможете лениво извлекать родительскую @OneToOne ассоциацию.

Однако чаще всего вам даже не нужна ассоциация на родительской стороне, если вы используете @MapsId на дочерней стороне:

@Entity(name = "PostDetails")
@Table(name = "post_details")
public class PostDetails {

@Id
private Long id;

@Column(name = "created_on")
private Date createdOn;

@Column(name = "created_by")
private String createdBy;

@OneToOne(fetch = FetchType.LAZY)
@MapsId
private Post post;

public PostDetails() {}

public PostDetails(String createdBy) {
createdOn = new Date();
this.createdBy = createdBy;
}

//Getters and setters omitted for brevity
}

С помощью @MapsId, id свойство в дочерней таблице служит как первичным ключом, так и внешним ключом к первичному ключу родительской таблицы.

Итак, если у вас есть ссылка на родительский Post объект, вы можете легко получить дочерний объект, используя идентификатор родительского объекта:

PostDetails details = entityManager.find(
PostDetails.class,
post.getId()
);

Таким образом, у вас не будет N + 1 проблем с запросами, которые могут быть вызваны mappedBy @OneToOne ассоциацией на родительской стороне.

Ответ 4

Вот что у меня работает (без инструментария):

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

Эта настройка работает лениво, то есть выбор выполняется только при вызове getPrevious() or 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); }

}
java hibernate jpa