1

I'm working on an Admin UI for an authorization server. One of the features is to display a list of who has logged in, and we are doing that by querying a database table where the currently issued refresh tokens are stored. A user can log in from multiple devices to the same application, generating multiple tokens. At the moment the requirements are NOT to break down this view by device, instead if a user has logged in at all, they are to be shown in the list. If we revoke access (one of the other requirements from this UI), then all devices will have their refresh tokens revoked.

Anyway, the main thing tripping me up is the query. I'm writing the query to pull back all the tokens for the specified user, but for each client only the most recent one is retrieved. ie, if there are 5 tokens for a given user/client combination, only the one with the most recent timestamp will be returned to the UI. I'm trying to do this entirely with JPQL in my SpringBoot/Hibernate backend, which is communicating with a Postgres database.

I can write this in SQL several different ways. Here are two forms of the query that return the same results:

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
);

The reason I've figured out multiple ways to do the query is because I'm trying to also figure out how to translate it into JPQL. I avoid doing native queries in Hibernate as much as possible, instead relying on the DB-agnostic JPQL syntax. However, I just can't figure out how to translate this to JPQL.

I know native queries and/or putting filter logic into my Java code are both options. However, I'm hoping this is possible with a standard JPQL query.

1

1 Answer 1

1

You can use this:

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);

Depending on your exact use case, I think this Blaze-Persistence Entity Views could come in handy here.

I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.

A DTO model for your use case could look like the following with 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();
    }
}

Querying is a matter of applying the entity view to a query, the simplest being just a query by id.

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

The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features

It will use a lateral join query behind the scenes which is the most efficient on 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
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.