#postgresql #hibernate #jpql
#postgresql #впасть в спящий режим #jpql
Вопрос:
Я работаю над пользовательским интерфейсом администратора для сервера авторизации. Одной из функций является отображение списка тех, кто вошел в систему, и мы делаем это, запрашивая таблицу базы данных, в которой хранятся выпущенные в данный момент токены обновления. Пользователь может входить с нескольких устройств в одно и то же приложение, генерируя несколько токенов. На данный момент требования НЕ состоят в том, чтобы разбивать это представление по устройствам, вместо этого, если пользователь вообще вошел в систему, они должны быть показаны в списке. Если мы отменим доступ (одно из других требований этого пользовательского интерфейса), то у всех устройств будут отозваны токены обновления.
В любом случае, главное, что сбивает меня с толку, — это запрос. Я пишу запрос, чтобы вернуть все токены для указанного пользователя, но для каждого клиента извлекается только самый последний. т. е., если для данной комбинации пользователь / клиент имеется 5 токенов, в пользовательский интерфейс будет возвращен только тот, у которого самая последняя временная метка. Я пытаюсь сделать это полностью с помощью JPQL в моем бэкэнде SpringBoot / Hibernate, который взаимодействует с базой данных Postgres.
Я могу написать это в SQL несколькими различными способами. Вот две формы запроса, которые возвращают одни и те же результаты:
select r1.*
from dev.refresh_tokens r1
join (
select r2.client_id, max(r2.timestamp) as timestamp
from dev.refresh_tokens r2
group by r2.client_id
) r3 on r1.client_id = r3.client_id and r1.timestamp = r3.timestamp
where r1.user_id = 1;
select r1.*
from dev.refresh_tokens r1
where r1.user_id = 1
and (r1.client_id, r1.timestamp) in (
select r2.client_id, max(r2.timestamp) as timestamp
from dev.refresh_tokens r2
group by r2.client_id
);
Причина, по которой я придумал несколько способов выполнения запроса, заключается в том, что я пытаюсь также выяснить, как перевести его в JPQL. Я стараюсь избегать выполнения собственных запросов в режиме гибернации, насколько это возможно, вместо этого полагаясь на синтаксис JPQL, не зависящий от базы данных. Однако я просто не могу понять, как перевести это в JPQL.
Я знаю, что собственные запросы и / или включение логики фильтра в мой Java-код — это оба варианта. Тем не менее, я надеюсь, что это возможно с помощью стандартного запроса JPQL.
Комментарии:
1. Ваш запрос, безусловно, не является оптимальным. Вам следует изучить оконные функции: postgresql.org/docs/current/functions-window.html / postgresql.org/docs/current/tutorial-window.html (ищите ранг): ваш запрос будет выглядеть следующим образом: выберите * из (выберите r1. *, (rank() over (разделение по r1.client_id порядок по r1.timestamp desc)) как r из dev.refresh_tokens r1) где r= 1
Ответ №1:
Вы можете использовать это:
select r1
from RefreshToken r1
where r1.user.id = 1
and r1.timestamp = (select max(r2.timestamp) from RefreshToken r2 where r2.user.id = r1.user.id);
В зависимости от вашего конкретного варианта использования, я думаю, что здесь могут пригодиться эти представления сущностей с сохранением блеска.
Я создал библиотеку, чтобы обеспечить легкое сопоставление между моделями JPA и моделями, определяемыми пользовательским интерфейсом или абстрактным классом, что-то вроде весенних прогнозов данных на стероидах. Идея заключается в том, что вы определяете свою целевую структуру (модель домена) так, как вам нравится, и сопоставляете атрибуты (геттеры) через выражения JPQL с моделью сущности.
Модель DTO для вашего варианта использования может выглядеть следующим образом с Blaze-Persistence Entity-Views:
@EntityView(User.class)
public interface UserDto {
@IdMapping
Long getId();
String getName();
@Limit(limit = "1", order = "timestamp DESC")
@Mapping("tokens")
RefreshTokenDto getLatestToken();
@EntityView(RefreshToken.class)
interface RefreshTokenDto {
@IdMapping
Long getId();
String getToken();
}
}
Запрос — это вопрос применения представления сущности к запросу, простейшим из которых является просто запрос по идентификатору.
UserDto a = entityViewManager.find(entityManager, UserDto.class, id);
Интеграция данных Spring позволяет использовать его почти как проекции данных Spring: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
Он будет использовать запрос бокового соединения за кулисами, который является наиболее эффективным в PostgreSQL:
select u1.id, u1.name, r1.id, r1.token
from dev.user u1
left join lateral (
select *
from dev.refresh_tokens r
where r.user_id = u1.id
order by r.timestamp desc
limit 1
) r1
where r1.user_id = 1