Spring boot using QueryDSL filter contains , not exact - spring-data

How to filter list with contains, not exact match string using QuerydslPredicate spring boot?
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
</dependency>
for example:
We find name
The predicate will be: group.name and user should pass exact name to search.
how to filter contains / like to a column? Thanks
#GetMapping("/list")
public ResponseEntity<org.springframework.data.domain.Page<GroupDto>> searchList(#QuerydslPredicate(root = Group.class) Predicate predicate, Pageable pageable) {
if (SecurityUtil.isSuperAdmin() || hasView()) {
Page<GroupDto> list = groupDtoservice.getList(predicate, pageable);
return ResponseEntity.ok(list);
} else {
throw new ForbiddenException();
}
}
public interface GroupDtoService {
Page<GroupDto> getList(Predicate predicate, Pageable pageable);
}
public Page<GroupDto> getList(Predicate predicate, Pageable pageable) {
log.info(String.valueOf(predicate.toString()));
log.info(String.valueOf(pageable));
Page<Group> group = groupRepository.findAll(predicate, pageable);
return group.map(this::convertToObjectDto);
}

Related

Use limit and skip in MongoRepository<Customer,String>

We are working on a project to get data from mongoDB. We have created repository class as below
#Repository
public interface CustomerRepository extends MongoRepository<Customer,String>{
List<Customer> customers = findByCustomerId(final String customerId);
}
We are looking to add skip/offset and limit parameters to be used as part of findByCustomerId method. where limit is used to define number of records returned and skip/offset defines the number of records after which we need to get the records.
Please help how we can get this implemented in best way using MongoRepository.
There are two ways to do this.
Use of #Aggregation annotation as mentioned in this answer.
https://stackoverflow.com/a/71292598/8470055
For example:
#Repository
public interface CustomerRepository extends MongoRepository<Customer,String>{
#Aggregation(pipeline = {
"{ '$match': { 'customerId' : ?0 } }",
"{ '$sort' : { 'customerId' : 1 } }",
"{ '$skip' : ?1 }",
"{ '$limit' : ?2 }"
})
List<Customer> findByCustomerId(final String customerId, int skip, int limit);
#Aggregation(pipeline = {
"{ '$match': { 'customerId' : ?0 } }",
"{ '$sort' : { 'customerId' : 1 } }",
"{ '$skip' : ?1 }"
})
Page<Customer> findCustomers(final String customerId, int skip, Pageable pageable);
}
The $match operator's query might need to be modified so that it better reflects the condition that needs to be satisfied by the matching documents.
Use Pageable argument in the query method and supply the PageRequest from the layer that calls the Repository method as shown in this answer.
https://stackoverflow.com/a/10077534/8470055
For the code snippet in the question this then becomes.
#Repository
public interface CustomerRepository extends MongoRepository<Customer,String> {
Page<Customer> findByCustomerId(final String customerId, Pageable pageable);
}
// -------------------------------------------------------
// Call the repository method from a service
#Service
public class CustomerService {
private final CustomerRepository customerRepository;
public CustomerService(CustomerRepository customerRepository) {
this.customerRepository = customerRepository;
}
public List<Customer> getCustomers(String customerId, int skip, int limit) {
// application-specific handling of skip and limit arguments
int page = 1; // calculated based on skip and limit values
int size = 5; // calculated based on skip and limit values
Page<Customer> page = customerRepository.findByCustomerId(customerId,
PageRequest.of(page, size, Sort.Direction.ASC, "customerId"));
List<Customer> customers = page.getContent();
/*
Here, the query method will retrieve 5 documents from the second
page.
It skips the first 5 documents in the first page with page index 0.
This approach requires calculating the page to retrieve based on
the application's definition of limit/skip.
*/
return Collections.unmodifiableList(customers);
}
}
The aggregation approach is more useful.
If the result is limited to a few documents then the query method can return List<Customer>.
If there a lot of documents then the query method can be modified to use Pageable argument that returns Page<Customer> to page over the documents.
Refer to both Spring Data and MongoDB documentation.
https://docs.spring.io/spring-data/mongodb/docs/3.2.10/reference/html/#mongo.repositories
https://docs.spring.io/spring-data/mongodb/docs/3.2.10/reference/html/#mongodb.repositories.queries.aggregation
https://docs.spring.io/spring-data/mongodb/docs/3.2.10/api/org/springframework/data/mongodb/repository/Aggregation.html
https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/domain/Pageable.html
https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/domain/PageRequest.html
MongoDB Aggregation - https://www.mongodb.com/docs/manual/meta/aggregation-quick-reference/
Dynamic Queries
Custom Spring Data repository implementation along with use of MongoTemplate should help in implementing dynamic queries.
Custom Repositories - https://docs.spring.io/spring-data/mongodb/docs/3.2.10/reference/html/#repositories.custom-implementations
MongoTemplate - https://docs.spring.io/spring-data/mongodb/docs/3.2.10/api/org/springframework/data/mongodb/core/MongoTemplate.html
A simple use case is to use a custom repository with the Query and SimpleMongoRepository classes.
CustomerRepository.java
#Repository
public interface CustomerRepository extends ResourceRepository<Customer, String> {
}
ResourceRepository.java
#NoRepositoryBean
public interface ResourceRepository<T, I> extends MongoRepository<T, I> {
Page<T> findAll(Query query, Pageable pageable);
}
ResourceRepositoryImpl.java
#SuppressWarnings("rawtypes")
public class ResourceRepositoryImpl<T, I> extends SimpleMongoRepository<T, I> implements ResourceRepository<T, I> {
private MongoOperations mongoOperations;
private MongoEntityInformation entityInformation;
public ResourceRepositoryImpl(final MongoEntityInformation entityInformation, final MongoOperations mongoOperations) {
super(entityInformation, mongoOperations);
this.entityInformation = entityInformation;
this.mongoOperations = mongoOperations;
}
#Override
public Page<T> findAll(final Query query, final Pageable pageable) {
Assert.notNull(query, "Query must not be null!");
long total = mongoOperations.count(query, entityInformation.getJavaType(), entityInformation.getCollectionName());
List<T> content = mongoOperations.find(query.with(pageable), entityInformation.getJavaType(), entityInformation.getCollectionName());
return new PageImpl<T>(content,pageable,total);
}
}
CustomerService.java
#RequiredArgsConstructor
#Service
public class CustomerService {
private final CustomerRepository repository;
/**
* #param customerId
* #param limit the size of the page to be returned, must be greater than 0.
* #param page zero-based page index, must not be negative.
* #return Page of {#link Customer}
*/
public Page<Customer> getCustomers(String customerId, int limit, int page) {
Query query = new Query();
query.addCriteria(Criteria.where("customerId").is(customerId));
return repository.findAll(query, PageRequest.of(page, limit, Sort.by(Sort.Direction.ASC, "customerId")));
}
public List<Customer> getCustomersList(String customerId, int limit, int page) {
Page<Customer> customerPage = getCustomers(customerId, limit, page);
return customerPage.getContent();
}
}
A reference with specific criteria:
https://dzone.com/articles/advanced-search-amp-filtering-api-using-spring-dat
I have used the Aggregation query with $skip and $limit, it works fine and is quite useful when you need to Paginate a complex piece of a query result. For simpler queries, I use the spring mongo template which takes a Query object. The query object takes a Pageable object where you define a page number and page size with a sorting option.
Criteria criterion = Criteria.where("field").is("value");//build your criteria here.
Query query = new Query(criterion);
Sort fieldSorting = Sort.by(Sort.Direction.DESC, "sortField"); // sort field
int pageNo = 1; //which page you want to fetch. NoOfPages = TotalRecords/PageZie
int pagesize = 10; // no of records per page
Pageable pageable = PageRequest.of(pageNo, pagesize, fieldSorting); // define your page
mongoTemplate.find(query.with(pageable), Object.class); // provide appropriate DTO class to map.
For mongo DB aggregation options - https://www.mongodb.com/docs/manual/reference/operator/aggregation/limit/
https://www.mongodb.com/docs/manual/reference/operator/aggregation/skip/
Another (maybe simpler) approach for limiting the results of the query is adding the filters in the method declaration when using MongoRepository. Both the keywords top and first can be used to reach this goal, specifying also the amount of results desired (or by omitting it, obtaining thus just one result).
The code below is an example, available in the docs.spring.io documentation for MongoRepositories (link below).
User findFirstByOrderByLastnameAsc();
User findTopByOrderByAgeDesc();
Page<User> queryFirst10ByLastname(String lastname, Pageable pageable);
Slice<User> findTop3ByLastname(String lastname, Pageable pageable);
List<User> findFirst10ByLastname(String lastname, Sort sort);
List<User> findTop10ByLastname(String lastname, Pageable pageable);
You can also apply pagination to your query (more details in the
documentation).
SOME EXTRA INFORMATION ABOUT SORTING:
As the other answers also gave some insight about the sorting, I would like to bring other options in this regard.
If your method will always sort the results in the same way, the sorting can be made by using the OrderBy keyword in your method declaration, followed by Asc or Desc depending on your use-case.
List<User> findFirst10ByLastnameOrderByAgeAsc(String lastname);
List<User> findFirst10ByLastnameOrderByAgeDesc(String lastname);
If you would like to sort your results dynamically, you can use the Sort argument on your method and provide.
List<User> findFirst10ByLastname(String lastname, Sort sort);
As an example, providing Sort.by(DESC, "age") in the method call will define { age : -1 } as the sort for the query.
References:
https://docs.spring.io/spring-data/mongodb/docs/3.2.10/reference/html/#repositories.query-methods

How to write criteria Query with multiple Or condition for a subdocument in mongoDB

I am trying to write a criteria query for document which contains subdocument in it. And I want to query on both the items of the document and the subdocuments with some and as well as multiple or conditions. Below is my document strucutre
public class ParentEntity {
#Id
private String id;
private String sName;
private String itemDesc;
private String localTimeStamp;
private boolean isStatus;
private List<ChildEntity> childEntity;
private List<String> itemList;
}
public class ChildEntity{
private String prc;
private LocalDate effDate;
}
Now I want to query on a condition which will satisfy the below condition
We will fetch if isStatus is true
We will fetch if effDate is today.
We will fetch if localTimeStamp= null or localTimeStamp is empty or localTimeStamp field does not exists in mongoDb collection
so in one expression the condition is 1 and (2 or 3)
Below is my code what i have done
Criteria criteria = new Criteria();
Query query = new Query();
criteria.add("isStatus").is(true);
criteria.andOperator(criteria.orOperator(Criteria.where("childentity.effDate").is(LocalDate.now),Criteria.where("localTimeStamp").is("");
query.addCriteria(criteria);
List<ParentEntity> list = mongoTemplate.find(query,ParentEntity.class);
This is giving me result while the isStatus is true and effDate is today and localTimeStamp is "" , but i want to check also the localTimeStamp=null and localTimeStamp filed does not exists in the document.
How I will include 3 or s in a single condition with one and operator ?
Someone help me , i am new in mongoDb.
I suggest investigating spring-boot-starter-data-search.
add the starter to your pom file:
<dependency>
<groupId>app.commerce-io</groupId>
<artifactId>spring-boot-starter-data-search-mongodb</artifactId>
<version>1.1.0</version>
</dependency>
enable the Search Repository:
#Configuration
#EnableMongoRepositories(repositoryBaseClass = SearchRepositoryImpl.class)
public class MyConfiguration {
}
make your repository extends SearchRepository
#Repository
public interface UserRepository extends SearchRepository<UserDocument, String> {
}
use this repository to get your data
String search = "address.name: myName or email: myEmail and (address.country: FR and address.city: Paris)";
Page<UserDocument> result = userRepository.findAll(search, pageable);
You can find a demo for MongoDB here
Disclaimer: I'm a contributor of spring-boot-starter-data-search

MongoDB - FindById is not working and giving null

I am using Spring Boot(V2.2.2.RELEASE) + Spring Data Mongo Example. In this example, I've records like below
{
"_id" : ObjectId("5cb825e566135255e0bf38a4"),
"firstName" : "John",
"lastName": "Doe"
}
My Repository
#Repository
public interface EmployeeRepository extends CrudRepository<Employee, ObjectId>{
Employee findById(String id);
}
Code
Employee findById = employeeRepository.findById("5cb825e566135255e0bf38a4");
System.out.println(findById);
Even below code not working
Query query = new Query(Criteria.where("id").is(new ObjectId("5cb825e566135255e0bf38a4")));
List<Employee> find = mongoTemplate.find(query, Employee.class);
Seems there might be two issues
ObjectId should be used
employeeRepository.findById(new ObjectId("5cb825e566135255e0bf38a4"))
ID field goes with underscore
new Query(Criteria.where("_id").is(new ObjectId("5cb825e566135255e0bf38a4")))
I'm not a Java guy, so might miss smth, but at least give it a try :)
Using the input document with _id: ObjectId("5cb825e566135255e0bf38a4") you can use either of the approaches. Assuming there is the document in the employee collection you can query by the _id's string value.
public class Employee {
#Id
private String id;
private String name;
// constructors (with and with arguments)
// get methods
// override toString()
}
// Spring-data app using MongoTemplate
MongoOperations mongoOps = new MongoTemplate(MongoClients.create(), "test");
Query query = new Query(where("id").is("5cb825e566135255e0bf38a4"));
Employee emp = mongoOps.findOne(query, Employee.class);
-or-
MongoRepository
interface extends CrudRepository and exposes the capabilities of the
underlying persistence technology in addition to the generic
persistence technology-agnostic interfaces such as CrudRepository.
#Repository
public interface EmployeeRepository extends MongoRepository<Employee, String> {
#Query("{ 'id' : ?0 }")
Optional<Employee> findById(String id);
}
// Spring-data app using the Repository
Optional<Employee> empOpt = employeeRepository.findById("5cb825e566135255e0bf38a4");
if (empOpt.isPresent()) {
System.out.println(empOpt.get());
}
else {
System.out.println("Employee not found!");
}
I too faced the issue. Interestingly things are working fine with version 2.1.7RELEASE
<documentRepository>.findById(<StringID>) treats as String Id in 2.2.2RELEASE
while 2.1.7RELEASE transforms it to ObjectId and hence the find query works.

Spring Data JPA - PagingAndSortingRepository and Querydsl Predicate not working with findByAttribute

I have the following Repository:
public interface TableRepository extends
PagingAndSortingRepository<Table, Integer>,
QuerydslPredicateExecutor<Table>,
QuerydslBinderCustomizer<QTable>
{
Page<Table> findAllByDatabaseId(Integer databaseId, Predicate predicate, Pageable pageable);
}
I'm trying to get all Tables with a certain databaseId which matches the given predicate. But I'm getting an IndexOutOfBoundsException:
java.lang.IndexOutOfBoundsException: Index: 1, Size: 1
at java.util.ArrayList.rangeCheck(ArrayList.java:657)
at java.util.ArrayList.get(ArrayList.java:433)
at java.util.Collections$UnmodifiableList.get(Collections.java:1309)
at org.springframework.data.jpa.repository.query.QueryParameterSetterFactory$CriteriaQueryParameterSetterFactory.create(QueryParameterSetterFactory.java:271)
at org.springframework.data.jpa.repository.query.ParameterBinderFactory.lambda$createQueryParameterSetter$1(ParameterBinderFactory.java:139)
at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
at java.util.Spliterators$ArraySpliterator.tryAdvance(Spliterators.java:958)
at java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:126)
at java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:498)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:485)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
at java.util.stream.FindOps$FindOp.evaluateSequential(FindOps.java:152)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:464)
That's the REST endpoint where I'm calling this method:
public Page<TableProjection> getTablesByDatabase(
#PathVariable("databaseId") final Integer databaseId,
final Pageable pageable,
#QuerydslPredicate(root = Table.class) final Predicate predicate)
{
tableRepository.findAllByDatabaseId(databaseId, predicate, pageable);
}
Perhaps, someone got Querydsl working with findBy-methods?
It works perfectly fine, if I'm just calling findAll like this:
Page<Table> findAll(Predicate predicate, Pageable pageable);
But it doesn't work with findBy:
Page<Table> findAllByDatabaseId(Integer databaseId, Predicate predicate, Pageable pageable);
This is not supported. You probably should just add the constraint on the DatabaseId to your Predicate.
One way to achieve that is to have a default implementation of your method, create a new Predicate from the one passed as an argument and use that in a call to findAll.
There is actually a JIRA issue about this (or something rather similar).
Thanks to Jens Schauder I figured it out. What I'm doing now is creating a new Predicate based on the existing one and add the constraint for the databaseId and then just call the standard findAll() method:
public Page<TableProjection> getTablesByDatabase(
#PathVariable("databaseId") final Integer databaseId,
final Pageable pageable,
#QuerydslPredicate(root = Table.class) final Predicate predicate)
{
QTable t = QTable.table;
BooleanBuilder where = new BooleanBuilder();
where.and(predicate).and(t.database.id.eq(databaseId));
final Page<Table> tables = tableRepository.findAll(where, pageable);
}

JPA Critera Query In Spring

public Product findProductById(String id , String subCategoryId)
{
return em.find(Product.class, id);
}
In this method pass two parameter. How to retrieve record form product table id and subCategoryId?
Actually I am retrieve record based on Id but apply subCategoryId (with and Condition) Error occour.
Please send also Link explain How it's Work? Thanks.
public List findProductIdSubCategoryIdCategoryId(String categoryId, String subCategoryId,String id)
{
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery criteria = cb.createQuery(Product.class);
Root product = criteria.from(Product.class);
cb.equal(product.get("subCategoryId"),subCategoryId);
Predicate csi = cb.and(cb.equal(product.get("categoryId"), categoryId), cb.equal(product.get("subCategoryId"), subCategoryId),
cb.equal(product.get("id"), id));
criteria.select(product).where(csi);
return em.createQuery(criteria).getResultList();
}
It's working fine and attached also link http://en.wikibooks.org/wiki/Java_Persistence/Criteria