Каков наилучший способ запроса столбца внешнего ключа?

Я использую JPA и Querydsl для динамического создания запросов на основе критериев, которые передаются через REST API. По большей части это работает нормально, но я не уверен, как лучше всего запрашивать объекты на основе значения в столбце внешнего ключа.

Чтобы проиллюстрировать сценарий, рассмотрим следующие (упрощенные) объекты.

@Entity
@Table(name = "division")
public class Division {
    @Id
    private Long id;

    @Column(unique = true)
    private String uniqueKey;

    @OneToMany(mappedBy = "division")
    private List<DivisionGroup> groups;
}

@Entity
@Table(name = "division_group")
public class DivisionGroup {
    @ManyToOne
    @JoinColumn(referencedColumnName = "uniqueKey")
    private Division division;
}

Обратите внимание, что на Division ссылается не id, а uniqueKey. Скажем так, у меня есть на это свои причины.

Теперь я хотел бы выполнить запрос, подобный следующему:

select * from division_group where division = 'someUniqueKey'

Кажется достаточно простым, но для использования JPA требуется объединение и знание того, какое поле содержит значение внешнего ключа (в данном случае uniqueKey). Я предполагаю, что запрос JPA будет похож на подход 1.

Обратите внимание, что я не использую сгенерированные Q-типы, потому что типы определяются динамически на основе запроса.

Подход 1 — JPAQuery

String divisionKey = "abc"; // the division's uniqueKey value from the request

// entity classes would be determined dynamically; hardcoded for this example
PathBuilder division = new PathBuilder<>(Division.class, "division");
PathBuilder divisionGroup = new PathBuilder<>(DivisionGroup.class, "divisionGroup");

JPAQuery query = new JPAQuery(entityManager)
    .from(divisionGroup)
    .innerJoin(divisionGroup.get("division"), division)
    .where(division.get("uniqueKey").eq(divisionKey))
    .list(divisionGroup);

У меня есть следующие проблемы с этим подходом:

  • Присоединение кажется ненужным (но я мог бы с этим жить)
  • Это требует от меня знания того факта, что поле uniqueKey используется в качестве столбца соединения. Это настоящая проблема, поскольку код должен уметь обрабатывать все виды сущностей с различными отображениями.
  • Я каким-то образом получаю критерии null=null, смешанные с запросом. Я предполагаю, что это может быть не связано, хотя.

В качестве альтернативы я рассмотрел возможность использования JPASQLQuery вместо JPAQuery. Это выглядит примерно так:

Подход 2 — JPASQLQuery

String divisionKey = "abc"; // the division's uniqueKey value from the request

MySQLTemplates templates = new MySQLTemplates();
JPASQLQuery jpasqlQuery = new JPASQLQuery(entityManager, templates);

CustomHibernateNamingStrategy namingStrategy = new CustomHibernateNamingStrategy();
Class entityClass = DivisionGroup.class; // hardcoded for example
String tableName;

if (entityClass.isAnnotationPresent(Table.class)) {
    tableName = namingStrategy.tableName(((Table) entityClass.getAnnotation(Table.class)).name());
} else {
    tableName = namingStrategy.classToTableName(entityClass.getSimpleName());
}

PathBuilder qDivisionGroup = new PathBuilder(entityClass, tableName);
Path sDivisionGroup = new PathImpl(entityClass, tableName);

List<DivisionGroup> groups = jpasqlQuery.from(sDivisionGroup)
    .where(Expressions.stringPath("division").eq(divisionKey))
    .list(qDivisionGroup);

Этот подход работает и не требует каких-либо знаний о том, как выглядит объект Division и что поле uniqueKey используется для отношения FK. Что мне не нравится в этом подходе:

  • Мне нужно выяснить имя таблицы. Было бы неплохо, если бы это можно было сделать неявно, так как я все равно использую объект JPA.
  • Я должен предоставить MySQLTemplates, что затрудняет переключение источников данных.

Есть ли лучший способ добиться того, что я пытаюсь сделать?


person JWK    schedule 09.03.2015    source источник


Ответы (1)


Для обратных полей сопоставления ассоциации можно использовать разыменование идентификатора. Например, divisionGroup.division.id = 1 является действительным JPQL/HQL и не требует описанного вами присоединения. Например, в QueryDSL это будет divisionGroup.get("division").get("id").eq(1). Это возможно, даже если ассоциация отображается внешним ключом (т. е. не первичным ключом). С вашим сопоставлением divisionGroup.get("division").get("uniqueKey") должен фактически генерировать действительный JPQL. Имейте в виду, что Hibernate только недавно начал поддерживать разыменование идентификатора для естественного идентификатора (я внес его сам), поэтому в более старых версиях Hibernate вы все равно увидите объединение, сгенерированное в созданном SQL-запросе.

person Jan-Willem Gmelig Meyling    schedule 09.03.2021