Как я могу получить внешний ключ из сопоставления JPA ManyToOne, не обращаясь к целевой таблице? - PullRequest
13 голосов
/ 15 октября 2010

У меня есть следующие два аннотированных класса, которые я использую для построения графика:

@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, я обращаюсь к объекту ребра, чтобы получить триггеры идентификаторов двух узловдва оператора SQL на ребро, когда поставщик JPA lazily * загружает связанные узлы.Поскольку у меня уже есть объекты узлов, а идентификаторы уже загружены из таблицы ребер, я хочу пропустить эти запросы, поскольку они требуют очень много времени для больших графов.

Я попытался добавить эти строки вкласс Edge, но мой провайдер JPA хочет, чтобы я сделал одно отображение только для чтения, и я не могу найти способ сделать это:

@Column(name = "ixNodeTo")
private long _ixNodeTo;

@Column(name = "ixNodeFrom")
private long _ixNodeFrom;

Я использую Eclipselinkи MySQL, если это имеет значение.


** Поведение по умолчанию для @ManyToOne на самом деле требует быстрой загрузки, см. Ответ Паскаля *

Ответы [ 5 ]

14 голосов
/ 12 июня 2013

Я получил три хороших ответа, которые были одинаково полезны, и к настоящему моменту ни один из них не проскользнул на вершину путем публичного голосования, поэтому я объединяю их здесь для единого всеобъемлющего ответа:

а) Изменить запрос

Вы можете загрузить весь график сразу, изменив запрос, тем самым предоставив провайдеру JPA возможность понять, что у него уже есть все в памяти и нет необходимости возвращаться в БД:

List<Node> nodes = em.createQuery(
        "SELECT DISTINCT n FROM Node n LEFT JOIN FETCH n._rgOutbound")
        .getResultList();

(через axtavt )

b) Использовать поля только для чтения для ФК

Загрузка FK в их собственные поля, как описано в вопросе, также будет работать, если, как требует поставщик JPA, поля объявлены только для чтения, что делается следующим образом:

@Column(name = "ixNodeTo", insertable = false, updatable = false)

(через bravocharlie )

в) Использовать доступ к собственности

Если вы используете доступ к свойству вместо доступ к полю , провайдер JPA также получает шанс понять, что у него уже есть FK и ему не нужно извлекать ссылочный объект , Короче говоря, доступ к свойству означает, что вы помещаете аннотации JPA на получатель, тем самым «обещая» провайдеру JPA, что ваш получатель не пойдет и получит доступ к остальной части объекта. Подробнее в этот вопрос . Это будет работать для Hibernate, и для Eclipselink, это будет работать (предполагается в оригинальном ответе, подтвержденном мной экспериментально) с включенным ткачеством. (через Pascal Thivent )


Кроме того, как Паскаль указывает в своем ответе , @ManyToOne, в отличие от моего первоначального поста, не ленивая загрузка, а готовая загрузка по умолчанию, и изменения, которые также потребуют ткачества .

11 голосов
/ 15 октября 2010

Вы пробовали

@Column(name = "ixNodeTo", insertable = false, updatable = false)
3 голосов
/ 15 октября 2010

Как получить внешний ключ из сопоставления JPA ManyToOne, не обращаясь к целевой таблице?

Теоретически, провайдер JPA должен иметь возможность не запускать запрос при вызове

someEdge.getNodeFrom().getId()

, поскольку у него уже есть идентификатор (как FK).

Я на 100% уверен, что Hibernate может (при условии, что вы используете доступ к свойству ). В случае EclipseLink я не знаю (если это произойдет, вероятно, потребуется ткачество).

Поскольку я определил отношение между двумя таблицами в JPA, доступ к объекту ребра для получения идентификаторов двух узлов вызывает два оператора SQL на ребро, когда поставщик JPA лениво загружает связанные узлы. Поскольку у меня уже есть объекты узлов, а идентификаторы уже загружены из таблицы ребер, я хочу пропустить эти запросы, так как они требуют очень много времени для больших графов.

Обратите внимание, что @ManyToOne по умолчанию использует стратегию EAGER. Если вы хотите сделать его LAZY, вам нужно явно его декальцировать (но опять же, для этого потребуется соткать ваши классы с помощью EclipseLink).

2 голосов
/ 15 октября 2010

Я думаю, вам следует попытаться оптимизировать свой запрос, а не изменять отображение. Например, следующий запрос извлекает весь граф за один раз (проверено в Hibernate):

List<Node> nodes = em.createQuery(
            "SELECT DISTINCT n FROM Node n LEFT JOIN FETCH n._rgOutbound")
            .getResultList();
0 голосов
/ 22 декабря 2015

Как насчет использования getReference ()?

Например:

Node fkNode = em.getReference(edge.getNodeFrom()); // [1]
fkNode.getId()

[1] Это не вызовет запрос SQL для получения nodeFrom

...