Как я могу получить внешний ключ из сопоставления JPA ManyToOne без попадания в целевую таблицу?
У меня есть следующие два аннотированных класса, которые я использую для построения графика:
@Entity
@Table(name = "Edge")
public class Edge
{
/* some code omitted for brevity */
@ManyToOne
@JoinColumn(name = "ixNodeFrom", nullable = false)
private Node _nodFrom;
@ManyToOne
@JoinColumn(name = "ixNodeTo", nullable = false)
private Node _nodTo;
/* some code omitted for brevity */
}
@Entity
@Table(name = "Node")
public class Node
{
/* some code omitted for brevity */
@OneToMany(mappedBy = "_nodTo")
private Set<Edge> _rgInbound;
@OneToMany(mappedBy = "_nodFrom")
private Set<Edge> _rgOutbound;
/* some code omitted for brevity */
}
Теперь, когда я создаю график, я выдаю два запроса для извлечения всех строк из любой таблицы и настройки дочерних/родительских ссылок, для которых мне нужны идентификаторы, хранящиеся в таблице Edge
.
Поскольку я определил связь между двумя таблицами в JPA, доступ к объекту edge для получения идентификаторов двух узлов вызывает два оператора SQL на каждый край, когда поставщик JPA lazily * загружает связанные узлы. Поскольку у меня уже есть объекты node, и идентификаторы уже загружены из таблицы краев, я хочу пропустить эти запросы, поскольку они занимают очень много времени для больших графиков.
Я попытался добавить эти строки в класс Edge
, но затем мой поставщик JPA хочет, чтобы я сделал одно сопоставление только для чтения, и я не могу найти способ, как это сделать:
@Column(name = "ixNodeTo")
private long _ixNodeTo;
@Column(name = "ixNodeFrom")
private long _ixNodeFrom;
Я использую Eclipselink и MySQL, если это имеет значение.
** Поведение по умолчанию для @ManyToOne
на самом деле является активной загрузкой, см. ответ Pascal *
Ответы
Ответ 1
У меня было три хороших ответа, которые были в равной степени полезны, и к настоящему времени никто не пронизан вершиной публичным голосованием, поэтому я объединяю их вместе для получения единого исчерпывающего ответа:
a) Измените запрос
Вы можете загрузить весь график сразу, изменив запрос, тем самым предоставив провайдеру JPA возможность понять, что он уже имеет все в памяти и не нуждается в возврате в БД:
List<Node> nodes = em.createQuery(
"SELECT DISTINCT n FROM Node n LEFT JOIN FETCH n._rgOutbound")
.getResultList();
(через axtavt)
b) Используйте поля только для чтения для FKs
Загрузка FK в свои собственные поля, как описано в вопросе, также будет работать, если, по мере того как поставщик JPA требует, поля объявляются как readonly, что делается следующим образом:
@Column(name = "ixNodeTo", insertable = false, updatable = false)
(через bravocharlie)
c) Использовать доступ к свойствам
Если вы используете доступ к свойствам вместо доступа к полю, у провайдера JPA также появляется возможность понять, что у него уже есть FK, и ему не нужно извлекать связанный объект. Короче говоря, доступ к ресурсам означает, что вы помещаете аннотации JPA на геттер, тем самым "обещая" провайдеру JPA, что ваш получатель не пойдет и не получит доступ к остальной части объекта. Подробнее в этот вопрос. Это будет работать для Hibernate, и для Eclipselink он будет работать (предполагается в исходном ответе, экспериментально подтвержденном мной) с включенным плетением. (через Pascal Thivent)
Кроме того, как Паскаль указывает в своем ответе, @ManyToOne
, вопреки моему первоначальному сообщению, не является ленивой загрузкой, но по-прежнему загружается по умолчанию, и изменение, которое потребует также плетения.
Ответ 2
Вы пробовали
@Column(name = "ixNodeTo", insertable = false, updatable = false)
Ответ 3
Как получить внешний ключ из сопоставления JPA ManyToOne без попадания в целевую таблицу?
Теоретически, поставщик JPA должен иметь возможность не вызывать запрос при вызове
someEdge.getNodeFrom().getId()
поскольку он уже имеет идентификатор (как FK).
Я на 100% уверен, что Hibernate может (при условии, что вы используете доступ к свойствам). В случае с EclipseLink я не знаю (если это так, то, вероятно, потребуется ткачество).
Поскольку я определил связь между двумя таблицами в JPA, доступ к объекту edge для получения идентификаторов двух узлов запускает два оператора SQL на каждый край, когда поставщик JPA лениво загружает связанные узлы. Поскольку у меня уже есть объекты node, и идентификаторы уже загружены из таблицы краев, я хочу пропустить эти запросы, поскольку они занимают очень много времени для больших графиков.
Обратите внимание, что @ManyToOne
по умолчанию использует стратегию EAGER
. Если вы хотите сделать это LAZY
, вы должны явно его декальтировать (но опять же, это потребует плетения ваших классов с помощью EclipseLink).
Ответ 4
Я думаю, вам следует попытаться оптимизировать ваш запрос, а не изменять сопоставление. Например, следующий запрос выводит весь граф сразу (проверяется в Hibernate):
List<Node> nodes = em.createQuery(
"SELECT DISTINCT n FROM Node n LEFT JOIN FETCH n._rgOutbound")
.getResultList();
Ответ 5
Как использовать getReference()?
Например:
Node fkNode = em.getReference(edge.getNodeFrom()); // [1]
fkNode.getId()
[1] Это не вызовет SQL-запрос для извлечения узла из