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

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

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

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

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


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

5 ответов

Решение

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

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

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

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

(через аксавт)

б) Используйте поля только для чтения для FKs

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

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

(через Бравочарли)

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

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


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

Ты пытался

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

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

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

someEdge.getNodeFrom().getId()

как это уже имеет идентификатор (как FK).

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

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

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

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

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

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

Например:

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

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

Другие вопросы по тегам