Spring data mongoDB: Get the distinct fields with pagination - spring

I want to implement method for getting distict values with pagination using spring MongoDb.
For exaple here I want to get just 2 distinct userMailBoxAddresses. But in result I received list of all distict values. So pagination does not work
Pageable pageable = PageRequest.of(0, 2);
Query query = new Query().with(pageable);
Page<String> page;
List<String> distinct = mongoTemplate.findDistinct(query, "userMailBoxAddress",
Entity.class, String.class);
I found that "The current Distinct() implementation only allows for bringing back ALL distinct values in the collection or matching a query, but there is no way to limit these results. "
Is it true? and maybe is some another way to implement getting distinct values with pagination?

for small collections, this could be an option:
Pageable pageable = PageRequest.of(0, 2);
GroupOperation groupOp = Aggregation.group("userMailBoxAddress");
SortOperation sortOp = Aggregation.sort(Direction.ASC, "_id");
SkipOperation skipOp = Aggregation.skip(pageable.getPageNumber() * pageable.getPageSize() * 1L);
LimitOperation limitOp = Aggregation.limit(pageable.getPageSize());
Aggregation aggregation = Aggregation.newAggregation(groupOp, sortOp, skipOp, limitOp);
List<String> distinct = mongoTemplate.aggregate(aggregation, Entity.class, Document.class)
.getMappedResults()
.stream()
.map(result -> (String)result.get("_id"))
.collect(Collectors.toList());

Related

delete in list via Panache in Quarkus

Here’s what I want to do
delete from table where id in list_of_ids
I know Hibernate HQL can do that
Long[] ids = {1L, 2L, 3L};
Query query = session.createQuery("delete from SysMenu where id in (:id)");
query.setParameterList("id", ids);
int i = query.executeUpdate();
But what can I do if I want to use Panache-ORM?
with panache you can always use a simplified query, something like
SysMenu.delete("delete from SysMenu where id in ?", ids);
should work (handwritten, not tested).
Here you can see the method definition
It works with Panache
Long[] ids = {1414151951951728640L, 1414152114971742208L};
List<Long> list = Arrays.asList(ids);
long rows = SysMenu.delete("id in (?1)", list);

How to get Distinct record from JPA

I have implemented a method which gives me specification, but what I want is the query should be as below:
Select Distinct *
From (another select query)
I generate query dynamically.
How do I perform the same using specification in Spring Boot?
Try something like this
Specification<T> spec = getSpecification();
Specification<T> distinctSpec = (root, query, cb) -> {
query.distinct(true);
return spec.toPredicate(root, query, cb);
};
if you want to get distinct records, you have to write a query like this in the repository.
The below query gives the distinct author from the post table.
#Query("select distinct author from Post")
List<String> findUniqueAuthor();
Write this in the repository
#Query(value = "Select Distinct * From (another select query)", nativeQuery = true)
List<Object> findUniqueData();

JPA Criteria api - Total records for concrete query within pagination

I am programming function for pagination in my repository layer. Function receive as parameters spring's pageable object and some value like this:
public Page<Foo> filterFoo(Pageable pageable, String value) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Foo> fooQuery = cb.createQuery(Foo.class);
Root<Foo> foo = fooQuery .from(Foo.class);
fooQuery .where(adding predicate for match value);
List<Foo> result = entityManager.createQuery(fooQuery )
.setFirstResult((pageable.getPageNumber() - 1) * pageable.getPageSize())
.setMaxResults(pageable.getPageSize())
.getResultList();
return new PageImpl<>(result, pageable, xxxx);
}
Function return spring's PageImpl object filled with my result. To PageImpl I also need set total count of objects which suit predicates. This count number have to be of course without maxResult and firstResult. Is possible create another database call with my fooQuery to get total database records for that query without limit? What is the best practise to use pageable and criteria api in JPA? Thank you in advice.
Because generated SQL uses aliases - you may need make separate query for get total count of rows.
For example:
CriteriaQuery<Long> countQuery = cb.createQuery(Long.class);
countQuery.select(cb.count(countQuery.from(Foo.class)));
if (Objects.nonNull(filters)) {
countQuery.where(filters);
}
return new PageImpl<>(result, pageable, em.createQuery(countQuery).getSingleResult());
where filters is equal to your adding predicate for match value expression.
Also, you may use a TupleQuery with custom SQL function for calculate count of rows in one select query.
Like this:
public class SqlFunctionsMetadataBuilderContributor implements MetadataBuilderContributor {
#Override
public void contribute(MetadataBuilder metadataBuilder) {
metadataBuilder.applySqlFunction(
"count_over",
new SQLFunctionTemplate(
StandardBasicTypes.LONG,
"(count(?1) over())"
)
);
}
}
and Criteria:
public Page<Foo> findAll(Specification<Foo> specification, Pageable pageable) {
CriteriaQuery<Tuple> cq = cb.createTupleQuery();
Root<Foo.class> fooRoot = cq.from(Foo.class);
cq.select(cb.tuple(fooRoot, cb.function("count_over", Long.class, fooRoot.get("id"))));
Predicate filters = specification.toPredicate(fooRoot, cq, cb);
if (Objects.nonNull(filters)) {
cq.where(filters);
}
TypedQuery<Tuple> query = em.createQuery(cq);
query.setFirstResult((int) pageable.getOffset());
query.setMaxResults(pageable.getPageSize());
List<Tuple> result = query.getResultList();
if (result.isEmpty()) {
return new PageImpl<>(List.of());
}
return new PageImpl<>(
result.stream().map(tuple -> (Foo) tuple.get(0)).collect(toUnmodifiableList()),
pageable,
(long) result.get(0).get(1)
);
}
See more about SQLFunction: https://vladmihalcea.com/hibernate-sql-function-jpql-criteria-api-query/ and Custom SQL for Order in JPA Criteria API

How can I specify a limit to a query and to be pageable

I would like my Rest API to retrieve for me the X last records in a mongo database with pagination. So that when a user wants the 1000 last records, he will only get the first 100 records on the first page, etc ...
But it doesn't work that way.
I want the url to look like that :
http://localhost/api/devices/{device}/topics/{topic}/last/{limit}
and not like :
http://localhost/api/devices/{device}/topics/{topic}/last?size={limit}
#Override
public Page<DeviceData> findFirstXByDeviceAndTopicOrderByDateDesc(String device, String topic, int x, Pageable pageable) {
Query query = new Query(Criteria.where(DEVICE_KEY).is(device).and(TOPIC_KEY).is(topic));
query.with(new Sort(Sort.Direction.DESC, DATE_KEY));
query.limit(x);
query.with(pageable);
List<DeviceData> list = mongoTemplate.find(query, DeviceData.class);
Page<DeviceData> page = new PageImpl<DeviceData>(list);
return page;
}
I get all the records with pagination...
How can I limit the results and hava pagination based on that result.

Filter Search query in Spring Mongo DB

In feed collection "likeCount" and "commentCount" are two column. I want to get all document where "likeCount" + "commentCount" greater than 100. How can I write the search filter query in Spring Mongo DB?
Below is my sample feed collection data.
{
"_id" : ObjectId("55deb33dcb9be727e8356289"),
"channelName" : "Facebook",
"likeCount" : 2,
"commentCount" : 10,
}
For compare single field we can write search query like :
BasicDBObject searchFilter = new BasicDBObject();
searchFilter.append("likeCount", new BasicDBObject("$gte",100));
DBCursor feedCursor = mongoTemplate.getCollection("feed").find(searchFilter);
Try this
db.collection.aggregate([{$project:{total:{'$add':["$likeCount","$commentCount"]}}},{$match:{total:{$gt:100}}}])
You would need to use the MongoDB Aggregation Framework with Spring Data MongoDB. In Spring Data the following returns all feeds with a combined likes and comments counts greater than 100, using the aggregation framework. :
Entities
class FeedsCount {
#Id String id;
String channelName;
long likeCount;
long commentCount;
long totalLikesComments;
//...
}
Aggregation
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;
Aggregation agg = newAggregation(Feed.class,
project("id", "channelName", "likeCount", "commentCount")
.andExpression("likeCount + commentCount").as("totalLikesComments"),
match(where("totalLikesComments").gt(100))
);
//Convert the aggregation result into a List
AggregationResults<FeedsCount> groupResults
= mongoTemplate.aggregate(agg, FeedsCount.class);
List<FeedsCount> results = groupResults.getMappedResults();
In the code above, first create a new aggregation via the newAggregation static factory method to which you pass a list of aggregation operations. These aggregate operations define the aggregation pipeline of your Aggregation.
As a first step, select the "id", "channelName", "likeCount", "commentCount" fields from the input collection with the project operation and add a new field "totalLikesComments" which is a computed property that stores the sum of the "likeCount" and "commentCount" fields.
Finally in the second step, filter the intermediate result by using a match operation which accepts a Criteria query as an argument.
Note that you derive the name of the input-collection from the Feed-class passed as first parameter to the newAggregation-Method.

Resources