Несколько подзапросов в конструкторе из основного запроса прерывают преобразование в спящий режим

#java #hibernate

#java #спящий режим

Вопрос:

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

 public class Statistics {
    private double maxPrice;
    private double minPrice;
    private double actualPrice;
    private double startPrice;
}
 

У него также есть конструктор.

     public Statistics(double maxPrice, double minPrice, double actualPrice, double startPrice) {
        this.maxPrice = maxPrice;
        this.minPrice = minPrice;
        this.actualPrice = actualPrice;
        this.startPrice = startPrice;
    }
 

Запрос выглядит некрасиво, но должен работать.

 @Query(value = "select new Statistics(max(price.value), min(price.value), "  
    "                 (select price.value as startPrice "  
    "                  from Price price "  
    "                  where price.date = (select min(date) from Price where price.item.id = :item_id )"  
    "                        and price.item.id = :item_id"  
    "                 ) as min_price,"  
    "                 (select price.value as endPrice "  
    "                  from Price price "  
    "                  where price.date = (select max(date) from Price where price.item.id = :item_id )"  
    "                        and price.item.id = :item_id"  
    "                 ) as max_price "  
    "               ) "  
    " from Price price"  
    " where price.item.id = :item_id")
 

Режим гибернации генерирует следующую трассировку исключения:

 Caused by: java.lang.IllegalArgumentException: org.hibernate.QueryException: could not instantiate class [Statistics] from tuple
    at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:138)
    at org.hibernate.query.internal.AbstractProducedQuery.list(AbstractProducedQuery.java:1514)
    at org.hibernate.query.Query.getResultList(Query.java:132)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.orm.jpa.SharedEntityManagerCreator$DeferredQueryInvocationHandler.invoke(SharedEntityManagerCreator.java:402)
    at com.sun.proxy.$Proxy222.getResultList(Unknown Source)
    at org.springframework.data.jpa.repository.query.JpaQueryExecution$CollectionExecution.doExecute(JpaQueryExecution.java:129)
    at org.springframework.data.jpa.repository.query.JpaQueryExecution.execute(JpaQueryExecution.java:91)
    at org.springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.java:136)
    at org.springframework.data.jpa.repository.query.AbstractJpaQuery.execute(AbstractJpaQuery.java:125)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:605)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.lambda$invoke$3(RepositoryFactorySupport.java:595)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:595)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:59)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:295)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139)
    ... 157 common frames omitted
Caused by: org.hibernate.QueryException: could not instantiate class [Statistics] from tuple
    at org.hibernate.transform.AliasToBeanConstructorResultTransformer.transformTuple(AliasToBeanConstructorResultTransformer.java:41)
    at org.hibernate.hql.internal.HolderInstantiator.instantiate(HolderInstantiator.java:85)
    at org.hibernate.loader.hql.QueryLoader.getResultList(QueryLoader.java:472)
    at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2506)
    at org.hibernate.loader.Loader.list(Loader.java:2501)
    at org.hibernate.loader.hql.QueryLoader.list(QueryLoader.java:504)
    at org.hibernate.hql.internal.ast.QueryTranslatorImpl.list(QueryTranslatorImpl.java:395)
    at org.hibernate.engine.query.spi.HQLQueryPlan.performList(HQLQueryPlan.java:220)
    at org.hibernate.internal.SessionImpl.list(SessionImpl.java:1508)
    at org.hibernate.query.internal.AbstractProducedQuery.doList(AbstractProducedQuery.java:1537)
    at org.hibernate.query.internal.AbstractProducedQuery.list(AbstractProducedQuery.java:1505)
    ... 178 common frames omitted
 

Наряду с тем, что это не работает, я заметил один интригующий факт.
Если я изменю запрос, чтобы возвращать то же значение, что и значение для первой даты и последней даты (я меняю в обоих подзапросах вызов функции min), ЭТО СРАБОТАЕТ.

     @Query(value = "select new Statistics(max(price.value), min(price.value), "  
        "                 (select price.value as startPrice "  
        "                  from Price price "  
        "                  where price.date = (select min(date) from Price where price.item.id = :item_id )"  
        "                        and price.item.id = :item_id"  
        "                 ) as min_price,"  
        "                 (select price.value as endPrice "  
        "                  from Price price "  
        "                  where price.date = (select min(date) from Price where price.item.id = :item_id )"  
        "                        and price.item.id = :item_id"  
        "                 ) as max_price "  
        "               ) "  
        " from Price price"  
        " where price.item.id = :item_id")
 

Необходимо упомянуть, что для тестирования в БД у меня есть одна запись, которая вернет одинаковый результат для обоих подзапросов.

Ответ №1:

Попробуйте исправить свой запрос следующим образом:

 @Query(value = "select new Statistics(max(price.value), min(price.value), "  
    "     (select distinct price.value as startPrice "  
    "      from Price price "  
    "      where price.date = (select min(date) from Price where price.item.id = :item_id )"  
    "      and price.item.id = :item_id"  
    "      ) as min_price,"  
    "      (select distinct price.value as endPrice "  
    "       from Price price "  
    "       where price.date = (select max(date) from Price where price.item.id = :item_id )"  
    "       and price.item.id = :item_id"  
    "      ) as max_price "  
    " ) "  
    " from Price price"  
    " where price.item.id = :item_id")
 

Похоже, вы получаете эту ошибку из-за повторяющихся строк, возвращаемых подзапросом.

Комментарии:

1. К сожалению, это не имеет никакого значения. Мое решение состояло в том, чтобы разделить исходный запрос на 4 отдельных запроса и собрать объект в сервис. Возможно, я буду изучать дальше, когда у меня будет больше времени.

Ответ №2:

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

 @Query(value = "select new Statistics(max(price.value), min(price.value), "  
"     (select distinct price.value as startPrice "  
"      from Price price "  
"      where price.date = (select min(date) from Price where price.item.id = :item_id )"  
"      and price.item.id = :item_id"  
"      ),"  
"      (select distinct price.value as endPrice "  
"       from Price price "  
"       where price.date = (select max(date) from Price where price.item.id = :item_id )"  
"       and price.item.id = :item_id"  
"      ) "  
" ) "  
" from Price price"  
" where price.item.id = :item_id")
 

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

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

Модель DTO для вашего варианта использования может выглядеть следующим образом с Blaze-Persistence Entity-Views:

 @EntityView(Price.class)
public interface Statistics {
    @Mapping("MAX(value)")
    double getMaxPrice();
    @Mapping("MIN(value)")
    double getMinPrice();
    @Limit(limit = "1", order = "date asc")
    @MappingCorrelatedSimple(
      correlated = Price.class,
      correlationBasis = "this",
      correlationExpression = "item.id = EMBEDDING_VIEW(item.id)",
      correlationResult = "value"
    )
    double getStartPrice();
    @Limit(limit = "1", order = "date desc")
    @MappingCorrelatedSimple(
      correlated = Price.class,
      correlationBasis = "this",
      correlationExpression = "item.id = EMBEDDING_VIEW(item.id)",
      correlationResult = "value"
    )
    double getLastPrice();
}
 

Запрос — это вопрос применения представления сущности к запросу, простейшим из которых является просто запрос по идентификатору.

Statistics a = entityViewManager.find(entityManager, Statistics.class, id);

Интеграция данных Spring позволяет использовать ее почти как проекции данных Spring: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features

 Statistics findByItemId(int itemId);
 

Это позволит использовать боковые соединения, если СУБД разрешает это, что обычно намного эффективнее, чем выполнение двух вложенных подзапросов. Результирующий SQL-запрос выглядит примерно так:

 select max(p.value), min(p.value), p1.value, p2.value
from price p
left join lateral (select * from price p0 where p0.item_id = p.item_id order by p0.date asc limit 1) p1 on 1=1
left join lateral (select * from price p0 where p0.item_id = p.item_id order by p0.date desc limit 1) p2 on 1=1
where p.item_id = ?
group by p1.value, p2.value