JPA: выберите в polymorphi c сущности с помощью JPQL, eclipselink и объединенного наследования, используя множественное понижение - PullRequest
1 голос
/ 18 апреля 2020

У меня есть интересное упражнение, пытающееся выбрать несколько производных сущностей, используя один запрос JPQL, в то время как с eclipselink 2.7.6.

Полиморфизм реализуется с использованием объединенного наследования. Диаграмма сущностей и классы java выглядят следующим образом:

+--------------+
|  MainEntity  |
+--------------+                        +--------------+
|              | --- myRef:OneToOne --- |  Referenced  |
+--------------+                        +--------------+
                                        |  r: string   |
                                        +--------------+
                                               ^
                                               |
                                   +-----------+-----------+
                                   |                       |
                            +--------------+        +--------------+
                            |  Derived1    |        |  Derived2    |
                            +--------------+        +--------------+
                            |  d1: string  |        |  d2: string  |
                            +--------------+        +--------------+

@Entity                          
@Table(name="MAIN_ENTITY")
public class MainEntity
{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "MAIN_ENTITY_ID")
    public Integer mainEntityId;

    @OneToOne(optional = true)
    @JoinColumn(name = "MY_REF", referencedColumnName = "REFERENCED_ID")
    public Referenced myRef;
}

@Entity
@Table(name="REFERENCED")
@Inheritance(strategy=InheritanceType.JOINED)
@DiscriminatorColumn(name="REFERENCED_TYPE",discriminatorType=DiscriminatorType.STRING)
public abstract class Referenced
{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "REFERENCED_ID")
    public Integer referencedId;

    @Column(columnDefinition = "TEXT", name = "R")
    public String r;
}

@Entity
@Table(name="Derived1")
@DiscriminatorValue("DERIVED_1")
public class Derived1 extends Referenced
{
    @Column(columnDefinition = "TEXT", name = "D1")
    public String d1;
}

@Entity
@Table(name="Derived2")
@DiscriminatorValue("DERIVED_2")
public class Derived2 extends Referenced
{
    @Column(columnDefinition = "TEXT", name = "D2")
    public String d2;
}

Моя цель - создать один запрос, в результате чего таблица будет иметь общие столбцы (столбцы ссылочной сущности). присутствует слева, а также отдельные столбцы производных сущностей, представленные справа в одной таблице.

Если я инициализировал данные следующим образом:

Derived1 d1 = new Derived1();
d1.r = "R set from Derived1";
d1.d1 = "D1 set from Derived1";
MainEntity me1 = new MainEntity();
me1.myRef = d1;

Derived2 d2 = new Derived2();
d2.r = "R set from Derived2";
d2.d2 = "D1 set from Derived2";
MainEntity me2 = new MainEntity();
me2.myRef = d2;

em.getTransaction().begin();
em.persist(d1);
em.persist(me1);
em.persist(d2);
em.persist(me2);
em.getTransaction().commit();

Используя SQL я могу получить нужную таблицу, используя LEFT JOIN операторы:

SELECT 
    m.MAIN_ENTITY_ID,
    r.REFERENCED_ID,
    r.R,
    d1.D1,
    d2.D2
FROM 
    REFERENCED r 
INNER JOIN
    MAIN_ENTITY m on m.MY_REF = r.REFERENCED_ID
LEFT JOIN 
    DERIVED1 d1 ON r.REFERENCED_ID = d1.REFERENCED_ID 
LEFT JOIN 
    DERIVED2 d2 ON r.REFERENCED_ID = d2.REFERENCED_ID

Результаты:

MAIN_ENTITY_ID REFERENCED_ID R                   D1                   D2                   
-------------- ------------- ------------------- -------------------- -------------------- 
2              1             R set from Derived1 D1 set from Derived1 [null]               
1              2             R set from Derived2 [null]               D1 set from Derived2 

Однако пока мне сложно работать с JPQL тоже самое. Я пытался использовать любую комбинацию операторов JPE TREAT и (LEFT) JOIN, мне вообще не повезло. Либо результирующее объединение SQL приводит к тому, что идентификаторы d1 и d2 становятся равными (естественно, безрезультатных результатов), либо я получаю слишком много результатов, все они являются перестановками целевого результата, к которому я стремлюсь.

Я мог бы воспроизвести результат SQL, используя JPQL, используя комбинацию операторов TREAT и UNION, например:

SELECT 
    m.mainEntityId,
    m.myRef.referencedId,
    m.myRef.r,
    TREAT(m.myRef AS Derived1).d1,
    null as d2
FROM 
    MainEntity m
UNION
SELECT 
    m.mainEntityId,
    m.myRef.referencedId,
    m.myRef.r,
    null as d1,
    TREAT(m.myRef AS Derived2).d2
FROM 
    MainEntity m

Результаты:

mainEntityId referencedId r                   d1                   d2                   
------------ ------------ ------------------- -------------------- ------------------
2            1            R set from Derived1 D1 set from Derived1 null               
1            2            R set from Derived2 null               D1 set from Derived2 

Однако повторяя запрос Несколько раз с соответствующими null выборками кажется неэффективным и подверженным ошибкам, потому что я вынужден повторять всю структуру для каждого подтипа. Особенно для более нормализованных моделей данных этот подход кажется чрезмерно не интуитивным.

Очевидно, я пытаюсь навязать парадигму SQL для JPQL, и, несмотря на небольшой успех, общее сообщение таково, что Я делаю что-то не так. Поэтому мой вопрос: есть ли лучший способ добиться этого с помощью JPQL? Если нет, что вы, люди, делаете в таком случае?

Заранее спасибо!

1 Ответ

1 голос
/ 18 апреля 2020

То, что вы пытаетесь сделать, может быть достигнуто с помощью следующего запроса JPQL и JOIN-ов:

    SELECT
        m.mainEntityId,
        r.referencedId,
        r.r,
        d1.d1,
        d2.d2
    FROM
        MainEntity m
        LEFT JOIN m.myRef r
        LEFT JOIN TREAT(m.myRef AS Derived1) d1
        LEFT JOIN TREAT(m.myRef AS Derived2) d2

Запрос возвращает те же две строки, что и в вашем примере SQL. Я должен был следовать этим правилам, чтобы получить правильный результат через JPQL:

  1. Не используйте более одного косвенного обращения в предложении SELECT или FROM (такие вещи, как SELECT m.myRef.r должны быть разбиты вплоть до JOIN m.myRef r и SELECT r.r).

  2. При объединении таблиц использовать атрибуты, а не имена объектов. Правильно: FROM MainEntity m LEFT JOIN m.myRef r, неверно: FROM MainEntity m LEFT JOIN Reference r. Объяснение: Необходимо указать точный атрибут, к которому вы присоединяетесь, поэтому JPA знает, какое условие ON генерировать. Если между MainEntity и Reference имеется более 1 отношения, то JPA не будет знать, к какому именно столбцу вы присоединяетесь, если вы не укажете его.

  3. Использование LEFT JOIN работает должным образом. Использование INNER JOIN, однако, заставляет Eclipselink генерировать некоторые странные вещи в SQL (он добавляет , Reference t2 к предложению JOIN, и результат неверно полон неожиданных перестановок). Я не могу объяснить, почему это происходит. INNER JOIN семантически верен, на мой взгляд, и это выглядит для меня как ошибка Eclipselink. Может быть, вы могли бы открыть отдельную проблему и спросить об этом.

...