Необъявленный путь на QueryDSL

Мой QueryDSL дает мне исключение:

2014-10-26 02:12:00,013 DEBUG  [ExceptionsHandler] org.springframework.dao.InvalidDataAccessApiUsageException: Undeclared path 'rolloutAdmin'. Add this path as a source to the query to be able to reference it.; nested exception is java.lang.IllegalArgumentException: Undeclared path 'rolloutAdmin'. Add this path as a source to the query to be able to reference it.

Я пробую код в QueryDSL для следующего запроса, который не может быть выполнен в JPQL:

@Query("SELECT new com.nsn.nitro.project.data.jpa.domain.RolloutMeta(r, count(b.id) as btsNbAll, ifnull(sum(b.status = com.nsn.nitro.project.data.utils.BTSStatus.PLANNED), 0) as btsNbPlanned, ifnull(sum(b.status = com.nsn.nitro.project.data.utils.BTSStatus.COMPLETED), 0) as btsNbCompleted, ifnull(sum(b.status = com.nsn.nitro.project.data.utils.BTSStatus.COMPLETED), 0) * 100.0 / count(b.id) as btsNbPercentage) FROM Rollout r, RolloutAdmin ra, BTS b WHERE b.rollout.id = r.id AND r.id = ra.rollout.id AND ra.admin = :admin GROUP BY r.id")
public Page<RolloutMeta> findMetaByAdmin(@Param("admin") Admin admin, Pageable page);

Вот полный запрос:

@Override
@Transactional(readOnly = true)
public Page<RolloutMeta> findMetaByAdmin(Admin admin, Pageable page) {
    JPAQuery query = new JPAQuery(rolloutRepository.getEntityManager());
    QRollout qRollout = QRollout.rollout;
    QRolloutAdmin qRolloutAdmin = QRolloutAdmin.rolloutAdmin;
    QAdmin qAdmin = QAdmin.admin;
    QBTS qBTS = QBTS.bTS;
    query.from(qRollout).innerJoin(qRolloutAdmin.rollout, qRollout).innerJoin(qRolloutAdmin.admin, qAdmin).innerJoin(qBTS.rollout, qRollout);
    BooleanBuilder builder = new BooleanBuilder();
    builder.and(qAdmin.eq(admin));
    query.where(builder)
    NumberExpression<Integer> statusPlanned = qBTS.status.when(com.nsn.nitro.project.data.utils.BTSStatus.PLANNED).then(new Integer(1)).otherwise(new Integer(0));
    NumberExpression<Integer> statusCompleted = qBTS.status.when(com.nsn.nitro.project.data.utils.BTSStatus.COMPLETED).then(new Integer(1)).otherwise(new Integer(0));
    NumberExpression<Integer> btsNbPlanned = statusPlanned.sum();
    NumberExpression<Integer> btsNbCompleted = statusCompleted.sum();
    NumberExpression<Integer> btsPercentage = statusCompleted.sum().divide(new Integer(100)).multiply(qBTS.count());
    query.orderBy(btsPercentage.desc());
    QRolloutMeta qRolloutMeta = new QRolloutMeta(qRollout, qBTS.count(), btsNbPlanned, btsNbCompleted, btsPercentage);
    List<RolloutMeta> resultList = query.list(qRolloutMeta);
    long total = resultList.size();        
    query.offset(page.getOffset());
    query.limit(page.getPageSize());
    resultList = query.list(qRolloutMeta);
    Page<RolloutMeta> rolloutMetas = new PageImpl<RolloutMeta>(resultList, page, total);
    return rolloutMetas;
}

Затем я попытался поместить qRolloutAdmin в query.from(qRolloutAdmin) как:

query.from(qRolloutAdmin).innerJoin(qRolloutAdmin.rollout, qRollout).innerJoin(qRolloutAdmin.admin, qAdmin).innerJoin(qBTS.rollout, qRollout);

Казалось, что это немного улучшило ситуацию, и на этот раз исключение будет почти таким же, но для bts:

2014-10-26 08:51:18,489 DEBUG  [ExceptionsHandler] org.springframework.dao.InvalidDataAccessApiUsageException: Undeclared path 'bTS'. Add this path as a source to the query to be able to reference it.; nested exception is java.lang.IllegalArgumentException: Undeclared path 'bTS'. Add this path as a source to the query to be able to reference it.

И поэтому я удалил внутреннее соединение на bts, чтобы оно было в построителе:

query.from(qRolloutAdmin).innerJoin(qRolloutAdmin.rollout, qRollout).innerJoin(qRolloutAdmin.admin, qAdmin);
BooleanBuilder builder = new BooleanBuilder();
builder.and(qBTS.rollout.eq(qRollout)).and(qAdmin.eq(admin));
query.where(builder);

Но он по-прежнему дает то же самое предыдущее исключение:

2014-10-26 09:08:00,397 DEBUG  [ExceptionsHandler] org.springframework.dao.InvalidDataAccessApiUsageException: Undeclared path 'bTS'. Add this path as a source to the query to be able to reference it.; nested exception is java.lang.IllegalArgumentException: Undeclared path 'bTS'. Add this path as a source to the query to be able to reference it.

Помимо попытки решить проблему, у меня возникают вопросы:

1- Does the entity sitting in the from method have to be a child one ?
2- Is there any difference between doing an innerJoin and an equal in the builder ?

Я использую QueryDSL 3.5.0

РЕДАКТИРОВАТЬ: Затем я заподозрил, что порядок параметров в innerJoin может иметь смысл, и поэтому я попробовал это, изображая слева направо:

query.from(qRollout);
query.innerJoin(qRollout, qRolloutAdmin.rollout);
query.innerJoin(qRolloutAdmin.admin, qAdmin);
query.innerJoin(qRollout, qBTS.rollout);

Что дало другое исключение на этот раз:

2014-10-26 10:02:04,354 DEBUG  [ExceptionsHandler] org.springframework.dao.InvalidDataAccessApiUsageException: rolloutAdmin.rollout is not a root path; nested exception is java.lang.IllegalArgumentException: rolloutAdmin.rollout is not a root path

Затем я попробовал несколько сущностей в методе from():

query.from(qRollout, qRolloutAdmin, qBTS);

но исключение осталось прежним.

EDIT2: я попытался указать innerJoin с помощью метода on(), как описано в 2.1.8. Раздел общего использования справочной документации:

query.from(qRollout);
query.innerJoin(qRolloutAdmin).on(qRolloutAdmin.rollout.eq(qRollout));
query.innerJoin(qRolloutAdmin).on(qRolloutAdmin.admin.eq(qAdmin));
query.innerJoin(qRollout).on(qBTS.rollout.eq(qRollout));

И получил исключение:

2014-10-26 10:24:11,098 DEBUG  [ExceptionsHandler] org.springframework.dao.InvalidDataAccessApiUsageException: rolloutAdmin is already used; nested exception is java.lang.IllegalStateException: rolloutAdmin is already used

EDIT3: я добавил метод on() для каждого из методов innerJoin:

QRolloutAdmin qRolloutAdmin = QRolloutAdmin.rolloutAdmin;
QRollout qRollout = QRollout.rollout;
QAdmin qAdmin = QAdmin.admin;
QBTS qBTS = QBTS.bTS;
query.from(qRollout);
query.innerJoin(qRolloutAdmin.rollout).on(qRolloutAdmin.rollout.eq(qRollout));
query.innerJoin(qRolloutAdmin.admin).on(qRolloutAdmin.admin.eq(qAdmin));
query.innerJoin(qBTS.rollout).on(qBTS.rollout.eq(qRollout));

Но он по-прежнему жалуется на необъявленный путь для rolloutAdmin:

Caused by: java.lang.IllegalArgumentException: Undeclared path 'rolloutAdmin'. Add this path as a source to the query to be able to reference it.

EDIT4: я пробовал следующее:

query.from(qRollout, qRolloutAdmin, qAdmin, qBTS);
query.innerJoin(qRolloutAdmin.rollout).on(qRolloutAdmin.rollout.eq(qRollout));
query.innerJoin(qRolloutAdmin.admin).on(qRolloutAdmin.admin.eq(qAdmin));
query.innerJoin(qBTS.rollout).on(qBTS.rollout.eq(qRollout));

Это все еще дает мне исключение:

Caused by: java.lang.IllegalArgumentException: org.hibernate.hql.internal.ast.InvalidWithClauseException: with clause can only reference columns in the driving table [select rollout, count(bTS), sum(case when bTS.status = ?1 then ?2 else ?3 end), sum(case when bTS.status = ?4 then ?2 else ?3 end), (sum(case when bTS.status = ?4 then ?2 else ?3 end) / ?5) * count(bTS)
from com.nsn.nitro.project.data.jpa.domain.Rollout rollout, com.nsn.nitro.project.data.jpa.domain.RolloutAdmin rolloutAdmin, com.nsn.nitro.project.data.jpa.domain.Admin admin, com.nsn.nitro.project.data.jpa.domain.BTS bTS

Обход проблемы соединения заключался в удалении внутренних операторов соединения и замене их предложениями and , как показано ниже:

builder.and(qRolloutAdmin.rollout.id.eq(qRollout.id)).and(qRolloutAdmin.admin.id.eq(qAdmin.id)).and(qBTS.rollout.id.eq(qRollout.id));

РЕДАКТИРОВАТЬ: проблема была решена по адресу:

QueryDSL не удалось определить тип данных для искомого оператора case


person Stephane    schedule 26.10.2014    source источник


Ответы (1)


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

Если вы хотите начать с RolloutAdmin, тогда

query.from(qRolloutAdmin)
     .innerJoin(qRolloutAdmin.rollout, qRollout)
     .innerJoin(qRolloutAdmin.admin, qAdmin);

Теперь вы можете использовать qRolloutAdmin, qRollout и qAdmin в своем запросе. qBTS еще не подключен к дереву свойств.

Должен ли объект, находящийся в методе from, быть дочерним?

Это должна быть корневая переменная, а не дочерняя.

Есть ли разница между выполнением innerJoin и равным в построителе?

Результирующий SQL отличается и может быть оптимизирован по-разному. Использование объединений является предпочтительным способом.

person Timo Westkämper    schedule 26.10.2014
comment
Могу я спросить, что мы подразумеваем под корневым путем/переменной? - person Stephane; 26.10.2014
comment
Возможно, я обновил свой вопрос после вашего комментария. Мой qBTS должен быть подключен к нему с помощью innerJoin. - person Stephane; 26.10.2014
comment
Интересно, почему я не могу начать с Rollout. Это: query.from(qRollout).innerJoin(qRolloutAdmin.rollout, qRollout) .innerJoin(qRolloutAdmin.admin, qAdmin); жалуется на необъявленный путь 'rolloutAdmin'. - person Stephane; 26.10.2014
comment
Я ищу документ 3.5.0/reference, чтобы попытаться понять логику обработки пути... есть немного об этом? - person Stephane; 26.10.2014
comment
root означает, что это не дочерний элемент, корневые пути являются переменными, а дочерние пути обычно являются свойствами. - person Timo Westkämper; 26.10.2014
comment
В справочных документах предполагается, что соединения JPQL уже знакомы. Вот вкратце: вводите переменные верхнего уровня (корневые) либо через from, либо через псевдоним (например, join(property, alias)), любое другое использование переменных является незаконным. - person Timo Westkämper; 26.10.2014
comment
Если вы хотите где-то использовать rolloutAdmin, введите его через from или псевдоним соединения. - person Timo Westkämper; 26.10.2014
comment
Таким образом, псевдоним соединения делает объект доступным в качестве корня для другого соединения... Объясняет ли это InvalidWithClauseException, которое у меня есть? - person Stephane; 27.10.2014
comment
Привет, Тимо, похоже, проблема связана с Hibernate hibernate.atlassian.net/browse/HHH-2772 Но, может быть, QueryDSL API также предлагает упомянутый обходной путь? - person Stephane; 27.10.2014
comment
Я мог бы обойти проблему присоединения к Hibernate, используя builder.and(qRolloutAdmin.rollout.id.eq(qRollout.id)).and(qRolloutAdmin.admin.id.eq(qAdmin.id)).and(qBTS. rollout.id.eq(qRollout.id)); вместо объединения операторов. - person Stephane; 28.10.2014
comment
Мне также пришлось обернуть параметры конструктора в операторы NumberTemplate.create(), иначе я бы получил: org.hibernate.QueryException: параметры поддерживаются только в предложениях SELECT при использовании как часть оператора INSERT INTO DML Этот поток помог мне: stackoverflow.com/questions/26234067/ - person Stephane; 28.10.2014
comment
Чтобы оставаться в рамках абстракции и не использовать имя столбца, я попробовал: new CaseBuilder().when(qBTS.status.eq(com.nsn.nitro.project.data.utils.BTSStatus.PLA‌​NNED)) но он произвел : случай, когда bts3_.status=COMPLETED с отсутствующими кавычками. То же самое для конструкции: qBTS.status.when(com.nsn.nitro.project.data.utils.BTSStatus.PLANNED), которая произвела: case bts3_.status, когда COMPLETED все еще без кавычек. - person Stephane; 28.10.2014
comment
Я видел, что EnumExpression-ы поддерживаются, но я не видел в справочной документации, как их использовать. - person Stephane; 28.10.2014
comment
Проблема была решена в stackoverflow.com/questions/26648491/ Это был Hibernate, который не обрезал его. - person Stephane; 28.11.2014