2016-11-23 7 views
3

Я переписываю NamedQuery в CriteriaQuery в hibernate-jpa-2.1. Исходный NamedQuery содержит порядок, который ссылается на подзапрос с псевдонимом.ORDER BY с использованием подзапроса в Hibernate JPA 2

select new ItemDto ( item.id, item.number, (select count(*) from ClickEntity as click where click.item.id = item.id) as clickCount ) from ItemEntity as item order by clickCount desc

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

public List<ItemDto> getItems() { 
    ... 
    CriteriaBuilder cb = entityManager.getCriteriaBuilder(); 
    CriteriaQuery<ItemDto> query = criteriaBuilder.createQuery(ItemDto.class); 
    Root<ItemEntity> item = query.from(ItemEntity.class); 

    query 
     .select(
      cb.construct(ItemDto.class, 
       item.get("id"), 
       item.get("number"), 
       getClickCount(cb, query, item).getSelection() 
      ) 
     ) 
     .orderBy(cb.desc(getClickCount(cb, query, item).getSelection())) 

    TypedQuery<ItemDto> typedQuery = entityManager.createQuery(query); 
    return typedQuery.getResultList(); 
} 

private Subquery<Long> getClickCount(CriteriaBuilder cb, CriteriaQuery<ItemDto> query, Root<ItemEntity> item) { 
    Subquery<Long> subquery = query.subquery(Long.class); 
    Root<ClickEntity> click = subquery.from(ClickEntity.class) 

    return subquery 
     .select(cb.count(click.get("id"))) 
     .where(cb.equal(click.get("item").get("id"), item.get("id"))); 
} 

Однако, когда вызова GetItems(), Hibernate бросает следующее исключение при создании TypedQuery:

org.hibernate.hql.internal.ast.QuerySyntaxException: unexpected AST node: query [...] 

Проанализированный запрос выглядит так:

select new ItemDto(
    generatedAlias0.id, 
    generatedAlias0.number, 
    (select count(generatedAlias1.id) from ClickEntity as generatedAlias1 where(generatedAlias1.item.id=generatedAlias0.id)) 
) 

from ItemEntity as generatedAlias0 

order by 
    (select count(generatedAlias2.id) from ClickEntity as generatedAlias2 where(generatedAlias2.item.id=generatedAlias0.id)) desc 

Несмотря на ошибку, этот запрос выглядит хорошо для меня. Я протестировал его без предложения order by, а затем он работает так, как ожидалось, поэтому ошибка определенно вызвана этим предложением. Однако, поскольку Подзапрос, очевидно, работает, мне трудно понять, в чем проблема.

То, что я пытался/считать:

  • Использование @PostConstruct установить @Transient поле ItemEntity; это не вариант, поскольку в фактическом приложении значение clickCount зависит от параметра Date.
  • Заказ после получения результатов; это не вариант, так как заказ должен произойти до применения (необязательного) предельного параметра
  • Не использовать getSelection(). Это имеет тот же эффект (даже тот же запрос).

Итак, неудивительно, что этот подход фактически поддерживается Hibernate или мне не хватает (возможно, более простой) альтернативы использовать результат подзапроса в качестве параметра порядка?

ответ

0

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

1. Крест присоединиться где положение

Создание дополнительного корня для запроса приведет перекрестное соединение. В сочетании с предложением where это приведет к внутреннему соединению, в то время как у вас все еще есть доступ к полю в корне. Добавляем больше, где предложения допускают дальнейшую фильтрацию.

public List<ItemDto> getItems() { 
    ... 
    CriteriaBuilder cb = entityManager.getCriteriaBuilder(); 
    CriteriaQuery<ItemDto> query = criteriaBuilder.createQuery(ItemDto.class); 
    Root<ItemEntity> item = query.from(ItemEntity.class); 
    //Extra root here 
    Root<ClickEntity> click = query.from(ClickEntity.class); 

    query 
     .select(
      cb.construct(ItemDto.class, 
       item.get("id"), 
       item.get("number"), 
       cb.count(click.get("id")) 
      ) 
     ) 
     //Required to make the cross join into an inner join 
     .where(cb.equal(item.get("id"), click.get("item").get("id"))) 
     //Required because an aggregate function is used in the select clause 
     .groupBy(item.get("id"), item.get("number")) 
     //Possibility to refer to root 
     .orderBy(cb.count(click.get("id"))); 
    ... 
} 

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

2. Добавление поля в ItemEntity

Добавив поле @OneToMany к ItemEntity, который относится к храповой сущности, можно создать левое соединение.Во-первых, обновить ItemEntity:

@Entity 
public class ItemEntity { 
    ... 
    @OneToMany(cascade = CascadeType.ALL) 
    //The field in the click entity referring to the item 
    @JoinColumn(name="itemid") 
    private List<ClickEntity> clicks; 
    ... 
} 

Теперь вы можете иметь JPA выполнить объединение для вас и использовать объединение для обозначения полей в ClickEntity. Кроме того, вы можете добавить дополнительные условия для соединения, используя join.on (...), и используя query.having(), вы сможете отфильтровывать элементы без кликов, как в первом подходе.

public List<ItemDto> getItems() { 
    ... 
    CriteriaBuilder cb = entityManager.getCriteriaBuilder(); 
    CriteriaQuery<ItemDto> query = criteriaBuilder.createQuery(ItemDto.class); 
    Root<ItemEntity> item = query.from(ItemEntity.class); 
    //Join on the clicks field. A left join also selects items with 0 clicks. 
    Join<ItemEntity, ClickEntity> clicks = item.join("clicks", JoinType.left); 
    //Use join.on if you need more conditions to the join 
    /*clicks.on(...) */ 

    query 
     .select(
      cb.construct(ItemDto.class, 
       item.get("id"), 
       item.get("number"), 
       cb.count(clicks.get("id")) 
      ) 
     ) 
     //Required because an aggregate function is used in the select clause 
     .groupBy(item.get("id"), item.get("number")) 
     //Uncomment to filter out items without clicks 
     /* .having(cb.gt(cb.count(clicks.get("id")), 0)) */ 
     //Refer to the join 
     .orderBy(cb.count(clicks.get("id"))); 
    ... 
} 

ли обращать внимание не встраивать переменными щелчки, так как это будет эффективно соединить таблицу щелчков на столе предметов дважды.

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

Смежные вопросы