How Slice<?> in JPA repository works? - spring-data-jpa

We need to send Pageable object that is fine.
We will use slice mainly if in UI you don't need to send no of pages, only prev & next.
But, how it internally works?
Page<Employee> findByFirstName(String firstName, Pageable pageable);
Slice<Employee> findByFirstName(String firstName, Pageable pageable);
Page will internally call query & again the same query with
SELECT COUNT(*) FROM (last query) to find total elements.
Similarly, what Slice's queries?

It is the same as for Page except that it doesn't perform the count.
It might be of interest that the paging, i.e. the limiting to a certain batch of the result is done by calling setFirstResult(int) and setMaxResult(int)

Related

Spring Boot Couchbase Reactive isn't supporting Pagination

I'm trying to implement Pagination in my Reactive WebFlux app and my DB is Couchbase.
The Spring Data Couchbase doc allows me to pass Pageable as an argument in my Repository.
Flux<Person> findByFirstnameOrderByLastname(String firstname, Pageable pageable);
However, when I try to implement it I get the below error:
Caused by: java.lang.IllegalStateException: Method has to have one of the following return types! [interface org.springframework.data.domain.Slice, interface java.util.List, interface org.springframework.data.domain.Page]
My Repository method looks like this:
Flux<Building> findAll(Pageable pageable);
However, if I use this workaround, I've no problem.
#Query("#{#n1ql.selectEntity} where #{#n1ql.filter} LIMIT $1 OFFSET $2")
Flux<Building> findAll(Integer limit, Integer offset);
Is this a bug? Or, am I using it wrong?
Spring Boot version: 2.2.7.RELEASE
Full Repository:
#Repository
public interface BuildingRepository extends ReactiveSortingRepository<Building, String> {
#Query("#{#n1ql.selectEntity} where #{#n1ql.filter} LIMIT $1 OFFSET $2")
Flux<Building> findAll(Integer limit, Integer offset);
//This works if I comment the below
Flux<Building> findAll(Pageable pageable);
}
The short answer is that the documentation is not current, and the best solution is to implement paging yourself with limit and offset as you have done. There is work underway to remedy this in https://jira.spring.io/browse/DATACOUCH-588 (it's not described there, but that's the tracking issue)
Even so, a more efficient way of paging is key-set pagination ( https://use-the-index-luke.com/no-offset ) - but you’ll need to implement that in your application. It uses the indexes to get the items beginning with the first one required, instead of “skipping” over the ones in prior pages.

Spring Data JPA: get first record using #Query annotation on query method

My query method returns list of entities:
#Query("select u from ProfileDMO p inner join p.userPrincipal u where p.id=:profileId")
List<UserPrincipalDMO> findUserPrincipalByProfileId(#Param("profileId") long profileId);
And I need only first result. Currently, I am using List.get(int index) to get first element.
Does anyone know how should I update my query method to return only first result?
Updated answer:
If you can't use a derived query where Spring Data JPA derives the query from the method name you can use a Pageable parameter to limit the results and a second method with a default implementation to hide the parameter and unwrap the result:
#Query("select u from ProfileDMO p inner join p.userPrincipal u where p.id=:profileId")
List<UserPrincipalDMO> internalFindUserPrincipalByProfileId(#Param("profileId") long profileId, Pageable page);
default UserPrincipalDMO findUserPrincipalByProfileId(long profileId){
return internalFindUserPrincipalByProfileId(
profileId,
PageRequest.of(0,1)
).get(0);
};
Of course, that leaves the internal implementation in your API, if you don't like that you always can fall back to a custom implementation.
Original answer:
You should be able to use query derivation, i.e. remove the #Query annotation and change your method to:
UserPrincipalDMO findFirstByUserPrincipalProfileId(#Param("profileId") long profileId);
If you don't like the name you can use a default method with your preferred name and delegate to the one I proposed.
If you don't want the method name at all in your interface you can provide a custom implementation.

JPA criteriabuilder group by 1

I'm trying to migrate my postgres native querys to use criteriabuilder instead.
What I want to achieve is:
select date_trunc('day',t.starttime) AS day, count(*) AS no_of_users from login_table t group by 1 order by 1
So far I I don't see how to build the group by 1 order by 1.
This is how far I've gotten:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<RequestPerWeek> cq = cb.createQuery(RequestPerWeek.class);
Root<TLogin> from = cq.from(TLogin.class);
String date = "week";
Expression<Calendar> dateTrunc=cb.function("date_trunc",Calendar.class,cb.literal(date), from.get(TLogin_.starttime).as(Calendar.class));
cq.select(cb.construct(RequestPerWeek.class,cb.count(from),dateTrunc));
I've tried several groupby alternatives, but noone works like I want it to :-|
best regards,
hw
We are using spring data jpa in our project and if you use it, there is no need to write criteria query for simple queries, you can simply write the query directly on top of your method and get the result. This approach is 'Using named parameters'
For example,
public interface UserRepository extends JpaRepository<User, Long> {
#Query("select u from User u where u.firstname = :firstname or u.lastname = :lastname")
User findByLastnameOrFirstname(#Param("lastname") String lastname,
#Param("firstname") String firstname);
}
Below link is useful for anyone who is using spring data jpa, if you are writing criteria query take a look if you can get your result using named parameters approach. This is simple and you write very less code.
http://docs.spring.io/spring-data/data-jpa/docs/1.4.x/reference/htmlsingle/#jpa.named-parameters

Is it possible to use SpringData MongoDB repository to execute an arbitrary query, with pagination?

We have a use case where a user can pass in arbitrary search criteria for a collection, and wants the output paged. Using Spring Data repositories, this is quite simple if we know ahead of time what attributes they may be searching on by simple extending MongoRepository, and declaring a:
Page<Thing> findByFooAndBarAndBaz(Type foo, Type bar, Type baz, Pageable page)
However, if we generate the query ourselves either using the fluent interface or constructing a mongo string and wrapping it in a BasicQuery class, I can not find a way to get that into a repository instance. There is no:
Page<Thing> findByQuery(Query q, Pageable page)
functionality that I have been able to see.
Nor can I see how to hook into the MongoTemplate querying functionality with the Page abstraction.
I'm hoping I don't have to roll my own paging (calculating skip and limit parameters, which I guess is not hard) and call into the template directly, but I guess I can if that's the best choice.
I don't think this can be done in the way I'd hoped, but I've come up with a workaround. As background, we put all our methods to do data access in a DAO, and some delegate to the repository, some to the template.
Wrote a DAO method which takes our arbitrary filter string (which I have a utility that converts it to standard mongo JSON query syntax.
Wrap that in a BasicQuery to get a "countQuery".
Use that countQuery to get a total count of records using MongoTemplate#count(Query, Class)
Append my paging criteria to create a "pageQuery" using Query#with(Pageable)
Run the pageQuery with MongoTemplate#find(Query, Pageable)
Get the List<T> result from that, the Pageable that was used for the query and the count returned from the countQuery run, and construct a new PageImp to return to the caller.
Basically, this (DocDbDomain is a test domain class for testing out document db stuff):
Query countQuery = new BasicQuery(toMongoQueryString(filterString));
Query pageQuery = countQuery.with(pageRequest);
long total = template.count(countQuery, DocDbDomain.class);
List<DocDbDomain> content = template.find(pageQuery, DocDbDomain.class);
return new PageImpl<DocDbDomain>(content, pageRequest, total);
You can use the #Query annotation to execute an arbitrary query through a repository method:
interface PersonRepository extends Repository<Person, Long> {
#Query("{ 'firstname' : ?0 }")
Page<Person> findByCustomQuery(String firstname, Pageable pageable);
}
Generally speaking, #Query can contain any JSON query you can execute via the shell but with the ?0 kind of syntax to replace parameter values. You can find more information on the basic mechanism in the reference documentation.
In case you can't express your query within the #Query-Annotation, you can use the Spring Repository PageableExecutionUtils for your custom queries.
For example like this:
#Override
public Page<XXX> findSophisticatedXXX(/* params, ... */ #NotNull Pageable pageable) {
Query query = query(
where("...")
// ... sophisticated query ...
).with(pageable);
List<XXX> list = mongoOperations.find(query, XXX.class);
return PageableExecutionUtils.getPage(list, pageable,
() -> mongoOperations.count((Query.of(query).limit(-1).skip(-1), XXX.class));
}
Like in the original Spring Data Repository, the PageableExecutionUtils will do a separated count request and wrap it into a nice Page for you.
Here you can see that spring is doing the same.

Complex Many-to-Many JPA CriteriaQuery

I have two entities, Ablum and Image, which are in many to many relationship.
I wanna make a criteria query that to get all Albums and the counts on how many Images they have.
I don't want to get all Albums first then loop the result to get the counts as there would be so many sql requests.
I've been working for 2 nights and complete lost. If cannot find a way out maybe I need to fallback to use SQL.
Thanks to digitaljoel's inspiration, I found that CriteriaBuilder has a method call "size" that can be put on collections. Below is the code:
CriteriaBuilder cb = getCriteriaBuilder();
CriteriaQuery<Object[]> query = cb.createQuery(Object[].class);
Root<AlbumEntity> albums = query.from(AlbumEntity.class);
query.select(cb.array(albums.get(AlbumEntity_.id), cb.size(albums.get(AlbumEntity_.images))));
query.groupBy(albums.get(AlbumEntity_.id));
Here the groupBy call is a must otherwise error will occur.
But this method is to load the IDs of AlbumEntity, not the entity itself. The Album entity can be load if below code is used:
query.select(cb.array(albums, cb.size(albums.get(AlbumEntity_.images))));
query.groupBy(albums.get(AlbumEntity_.id), ...);
The groupBy must include all properites of the album entity. And it still does not work if the album entity has blob type property.
I'm going to have to make some assumptions since you haven't posted your JPA mapping, so I'm assuming each album has a List<YourImageClass> images for the many to many mapping. With that, something like this would work.
select a, size(a.images) from Album a
That would return a List<Object[]> where List.get(i)[0] would be the album and List.get(i)[1] would be the corresponding size of the image collection.
Alternately, you could define a simple bean to select into. Something like
public class AlbumResult {
private Album album;
private Integer imageCount;
public AlbumResult( Album a, Integer size ) {
album = a;
imageCount = size;
}
// getters and setters here
}
Then you could do
select new AlbumResult(a, size(a.images)) from Album a;
I never deal with criteria queries, but the JPQL is simple enough it should be trivial to translate it into a criteria query.