#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