I have a spring-boot application, and I would like to have a dynamic query criteria, i.e., I want to create a method that can receive the following optional parameters: Age and Name. My problem is that, these parameters are optional, that means they can be NULL sometimes, and I wanted to build my query dynamically, e.g., if the parameters are not null or empty I put them in my query as criteria, otherwise I don't.
The solution that I found for this problem was basically to create a custom implementation for my repository (UserRepositoryCustomImpl), and build the query by myself. However I was wondering if there is no "spring oriented solution", like some annotations, or something like that in which I could say, "Hey use this Map, to build this query dynamically".
I feel I've lived your same situation: my HTML form has N filters but I want they only apply to the search if they are selected or changed. Once you model your form binded with it's respective selects, inputs, etc... from the View, if any of these fields aren't selected or have the default value, just want to ignore but still using the convenience and ease of the Spring Repository (in my case JpaRespository).
With my little Spring perspective I've tried to use the Specification approach (IMO better than implement your custom Repository). It works perfectly, but don't know if it's the best way.
Here is an official Spring example https://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/
And here a personal example a little simplified (and probably breaking some best coding practices, with the aim to be understandable):
MyObjectRepository.java
public interface MyObjectRepository extends JpaRepository<MyObject, Long>, JpaSpecificationExecutor<MyObject> { }
MyObjectServiceImpl.java
#Service("MyObjectService")
public class MyObjectServiceImpl implements MyObjectService {
#Autowired
MyObjectRepository myObjectRespository;
#Override
public Page<MyObject> getAllMyObjectByFields(int pageIndex, FormMyObject formMyObject) {
Specification<MyObject> spec = new Specification<MyObject>() {
#Override
public Predicate toPredicate(Root<MyObject> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
if (formMyObject == null) {
throw new IllegalStateException("At least one parameter should be provided to construct complex query");
}
List<Predicate> predicates = new ArrayList<Predicate>();
// only if the content is present add a criteria to the search, you decide/know if will be null, or empty...
if (formMyObject.getYourField() != null && formMyObject.getYourField().length() > 0) {
predicates.add(
builder.and(builder.equal(root.get("field"), formMyObject.getYourField())));
}
// add as many criteria as you want/need
if(){
predicates.add( ... );
}
Predicate[] predicatesArray = new Predicate[predicates.size()];
return builder.and(predicates.toArray(predicatesArray));
}
};
PageRequest page = new PageRequest(pageIndex, formMyObject.getMaxResult());
// using the built in findAll method from Repository with dynamic custom filters
return myObjectRespository.findAll(spec, page);
Related
I have a Couchbase-Document "Group" with a list of group-members-names. I want to query for all groups of one person. I managed to do it with N1QL ARRAY_CONTAINS - see in code example - but i hoped that i could generate the query from the method name as it is usual in Spring Data.
Any help is appreciated :)
I tried
public List<MyGroup> findAllByMembers(String member); and public List<MyGroup> findByMembers(String member); but they just return an empty list - i guess they try to match the whole "members" value and don't recognize it as a list -, no errors.
Code
My Document with a List field
#Data
#Document
public class MyGroup {
private String name;
private List<String> members = new ArrayList<>();
}
My Repository
#RepositoryDefinition(domainClass = MyGroup.class, idClass = String.class)
public interface MyGroupRepository extends CouchbaseRepository<MyGroup, String> {
//#Query("#{#n1ql.selectEntity} WHERE ARRAY_CONTAINS(members,$1) AND #{#n1ql.filter}")
public List<MyGroup> findAllByMembers(String member);
}
Expected
Given a "group1" with "member1" in members.
repository.findAllByMembers("member1"); should return ["group1"].
Couchbase is limited by the Spring Data specification. Unfortunately, we can't simply add new behaviors to it (if you switch to a relational database, it has to work with no breaking points). So, whenever you need to query something that has a N1QL specific function/keyword, you have to write a query via #Query
I had a requirement in one of my interview tests using Spring Boot where I had to create an endpoint that accepts a bunch of optional request params and then returns a list of cars based on these parameters like car model, license plate, engine type, manufacturer, driver, the company it was rent to etc etc. And car, driver and manufacturer are all separate entities.
I implement this functionality in JPARepository with a single JPQL query implementing LEFT JOINS and filter in where clause like licensePlate = licensePlateParameter OR licensePlatParameter is null etc.
The solution was working however the interviewer said the solution was scalable and maintainable. I should have implemented it using predicates. Can someone show me an example how could I implement such functionality using predicates that is easier to maintain? Some examples with code would be greatly appreciated.
I thought I was smart by catering both the optional parameters and found records with in a single call by checking if the parameter is null or not. Another question related to that I have in mind is it really a good practice to get all the records from DB and then filter it using predicates? Also how to we filter when we have multiple objects/entities involved, predicates can be created for a single type.
#Query("SELECT d FROM Driver d LEFT JOIN d.car c WHERE (d.name = :name OR :name is null) "
+ "and (c.licensePlate = :licensePlate OR :licensePlate is null) "
+ "and (c.rating = :rating OR :rating is null) " and so on
List<Driver> findByAttributes(#Param("name") String name,
#Param("licensePlate") String licensePlate,
#Param("rating") Integer rating,
and so on);
Spring has a wrapper around the JPA criteria API (that uses predicates) and is called the specification API.
What you can do when writing specifications is the following, write a specification for each criteria:
public static Specification<Car> withLicensePlate(String licensePlate) {
return (root, query, cb) -> licensePlate == null ? null : cb.equal(root.get("licensePlate"), licensePlate);
}
public static Specification<Car> withRating(String rating) {
return (root, query, cb) -> rating == null ? null : cb.equal(root.get("rating"), rating);
}
public static Specification<Car> withName(String name) {
return (root, query, cb) -> name == null ? null : cb.equal(root.get("name"), name);
}
it also allows you to write a join operation as well:
public static Specification<Car> withSeatType(String type) {
return (root, query, cb) -> {
return type == null ? null : cb.equal(root.join("interior", JoinType.LEFT).get("type"), type);
};
}
You can return null within a criteria, which allows you to make these specifications "optional". After that, you can use Specifications.where() to combine these criteria:
Specification<Car> spec = Specifications
.where(withLicensePlate(licensePlate))
.and(withRating(rating))
.and(withName(name))
.and(withSeatType(seatType));
If you write separate specifications like I did in this example, you can re-use them where necessary. Otherwise, you'll have to write operation-specific specifications, and the interviewer might not find that scalable either.
After writing the specifications, you have to extend your repository from the JpaSpecificationExecutor interface and use the findAll(Specification) method.
You can use the dynamic query in Spring JPA as like this:
public List<Employee> findByCriteria(String employeeName,String employeeRole){
return employeeDAO.findAll(new Specification<Employee>() {
#Override
public Predicate toPredicate(Root<Employee> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
List<Predicate> predicates = new ArrayList<>();
if(employeeName!=null) {
predicates.add(criteriaBuilder.and(criteriaBuilder.like(root.get("employeeName"), "%"+employeeName+"%")));
}
if(employeeRole!=null){
predicates.add(criteriaBuilder.and(criteriaBuilder.equal(root.get("employeeRole"), employeeRole)));
}
return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
}
});
}
For this you need to implement JpaSpecificationExecutor in your repository.
Here is detail explanation for Spring JPA dynamic query
You could use Criteria Api instead JPQL.
For example refer to the example 1
https://www.programcreek.com/java-api-examples/index.php?api=javax.persistence.criteria.Predicate
I'm using Spring Data JPA v1.10.2
And there's a use-case:
ClientDao.java:
List<Client> getClientsByUnitsIn(#NonNull Collection<Unit> units);
This method generates an SQL query like this one:
SELECT * FROM clients WHERE units in (?1)
There's a similar case when I add #Query annotation for the repository method:
#Query("SELECT c FROM Client c WHERE c.unit IN (?1)")
List<Client> getSpecificClients(#NonNull Collection<Unit> units)
But in many cases parameter units may be empty. And in such cases the method should return empty result, but it just fail with a message about an erroneous SQL statement.
I use a workaround: adding a default method to the repository like this one:
default List<Client> getSpecificClientsOrEmpty(#NonNull Collection<Unit> units){
if (units.isEmpty) {
return emptyList();
}
return getSpecificClients(units);
}
But I don't like this workaround:
I have to create one extra method for each case
I have to check that only default method is using in code, as there's no compile-time checking, and if I miss some using, I get a runtime Exception.
Does anybody have a better solution?
1) Write your own query with the boiler plate code in the getSpecificClients() repository implementation :
public List<Client> getSpecificClients(#NonNull Collection<Unit> units){
if (units.isEmpty()) {
return emptyList();
}
return em.createQuery("SELECT c FROM Client c WHERE c.unit IN (?1)", Unit.class)
.setParameter(1, units)
.getResultList();
}
If this pre-processing is a uncommon requirement in your repository, this way should be favored.
It is a little verbose way but it is still acceptable for a handful of cases.
2) Make it in a transverse way with AOP.
Define a Aspect to do this processing before each method you need :
if (units.isEmpty) {
return emptyList();
}
Note that this way should be used only if the pre-processing requirement occurs frequently enough as it increases the application complexity and the general setup.
3) You could create a generic default method in a base interface repository that accepts a Function as parameter to be able to passe to the method any method to execute :
#SuppressWarnings("unchecked")
default<T, U> List<U> selectWithIn(Collection<T> valueForInClause, Function<Collection<T>, List<U>> function) {
if (valueForInClause.isEmpty()) {
return new ArrayList<U>();
}
return function.apply(valueForInClause);
}
In ClientDAO class you would have still this one :
#Query("SELECT c FROM Client c WHERE c.unit IN (?1)")
List<Client> getSpecificClients(#NonNull Collection<Unit> units)
And in the client code of the DAO you could invoke the selectWithIn() method in this way :
private ClientDAO clientDAO;
...
List<Unit> units = ...;
List<Client> clients = clientDAO.selectWithIn(units, (o) -> clientDAO.getSpecificClients(o));
It is not too much verbose, it spares some code lines but I don't like really this way as it makes a little more complex unit tests of the DAO client classes.
I have had the misfortune of working in Java for some time, coming from the .net world. Ranting aside, I am simply looking to implement a Repository that can handle use of predicates and must have pagination. I am unable to find a good way to do this.
// IContactRepository.java
public interface IContactRepository extends Repository<Contact,Long> {
}
// Application.java
contactRepo.findAll(predicate, new PageRequest(0,10));
I want to to be able to find contacts with contact name containing search term or contact phone number containing search term and then get first 10 matches.
In the .net world, if I was not using an orm I would use sql server's awesome TSQL to get what I want but stuck with Oracle here. I would otherwise use some ORM and pass a lambda to the query function as predicate.
In my configuration I am also using JPA and spring. (FOR STATIC PREDICATES. If you want to add predicates(search terms) dynamically please let me know.)
// IContactRepository.java
public interface IContactRepository extends CrudRepository<Contact,Long>, PagingAndSortingRepository<Contact, Long> {
List<Contact> findByContactNameLikeAndContactPhoneLike(String name, String phone, Pageable pageable)
}
I tried Pageable with CrudRepo and it works fine.
And for the lambda you are right :)
In my configuration your implementation looks like this :
IContactRepository contactRepo = context.getBean(IContactRepository.class);
List<Contacts> results = contactRepo.findByContactNameLikeAndContactPhoneLike("%CA%","%090%" , new PageRequest(1, 20));
http://docs.spring.io/spring-data/data-commons/docs/1.6.1.RELEASE/reference/html/repositories.html
Please have a look Query creation under 1.2.2 Defining query methods
I am guessing you are looking at Predicate because you want to be able to execute any arbitrarily complex query.
However there is no findAll(Predicate, Pageable) method.
I suggest that you check out Specification and JpaSpecificationExecutor. Your code would look like this:
public interface IContactRepository extends JpaRepository<Contact,Long>, JpaSpecificationExecutor<Contact> {
}
Then you would have access to the method findAll(Specification, Pageable). And as per your requirement, Specification is a Functional Interface, so you can use a lambda to easily pass in an implementation.
Check out section 2.5 from the documentation for more details.
Here is the Javadoc of Specification and here is the Javadoc of JpaSpecificationExecutor
Also if you have to endure the pain of Java, you should probably drop the I in IContactRepository :). Java code usually forgoes that .NET practice
Thats my implementation maybe help to you:
#Override
public Page<Advert> findActiveAdverts(String searchValue, String availability, Long branchId, Long categoryId, Pageable pageable) {
Page<Advert> adverts = advertRepository.findAll(new Specification<Advert>() {
#Override
public javax.persistence.criteria.Predicate toPredicate(Root<Advert> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
List<Predicate> predicates = new ArrayList<>();
predicates.add(cb.equal(root.get(Advert_.ACTIVE), true));
if (!StringUtils.isEmpty(searchValue)) {
predicates.add(cb.or(
cb.like(root.get(Advert_.TITLE), "%" + searchValue + "%"),
cb.like(root.get(Advert_.EXPLANATION), "%" + searchValue + "%"))
);
}
return cb.and(predicates.toArray(new Predicate[predicates.size()]));
}
}, pageable);
return adverts;
}
I am trying to incorporate Spring-Data-JPA into my project.
One thing that confuses me is how do I achieve setMaxResults(n) by annotation ?
for example, my code:
public interface UserRepository extends CrudRepository<User , Long>
{
#Query(value="From User u where u.otherObj = ?1 ")
public User findByOtherObj(OtherObj otherObj);
}
I only need to return one (and only one) User from otherObj, but I cannot find a way to annotate the maxResults. Can somebody give me a hint ?
(mysql complains :
com.mysql.jdbc.JDBC4PreparedStatement#5add5415: select user0_.id as id100_, user0_.created as created100_ from User user0_ where user0_.id=2 limit ** NOT SPECIFIED **
WARN util.JDBCExceptionReporter - SQL Error: 0, SQLState: 07001
ERROR util.JDBCExceptionReporter - No value specified for parameter 2
)
I found a link : https://jira.springsource.org/browse/DATAJPA-147,
I tried but failed. It seems not possible now?
Why is such an important feature not built into Spring-Data?
If I implement this feature manually:
public class UserRepositoryImpl implements UserRepository
I have to implement tons of predefined methods in CrudRepository, this would be terrible.
environments : spring-3.1 , spring-data-jpa-1.0.3.RELEASE.jar , spring-data-commons-core-1.1.0.RELEASE.jar
As of Spring Data JPA 1.7.0 (Evans release train).
You can use the newly introduced Top and First keywords that allow you to define query methods like this:
findTop10ByLastnameOrderByFirstnameAsc(String lastname);
Spring Data will automatically limit the results to the number you defined (defaulting to 1 if omitted). Note that the ordering of the results becomes relevant here (either through an OrderBy clause as seen in the example or by handing a Sort parameter into the method). Read more on that in the blog post covering new features of the Spring Data Evans release train or in the documentation.
For previous versions
To retrieve only slices of data, Spring Data uses the pagination abstraction which comes with a Pageable interface on the requesting side as well as a Page abstraction on the result side of things. So you could start with
public interface UserRepository extends Repository<User, Long> {
List<User> findByUsername(String username, Pageable pageable);
}
and use it like this:
Pageable topTen = new PageRequest(0, 10);
List<User> result = repository.findByUsername("Matthews", topTen);
If you need to know the context of the result (which page is it actually? is it the first one? how many are there in total?), use Page as return type:
public interface UserRepository extends Repository<User, Long> {
Page<User> findByUsername(String username, Pageable pageable);
}
The client code can then do something like this:
Pageable topTen = new PageRequest(0, 10);
Page<User> result = repository.findByUsername("Matthews", topTen);
Assert.assertThat(result.isFirstPage(), is(true));
Not that we will trigger a count projection of the actual query to be executed in case you use Page as return type as we need to find out how many elements there are in total to calculate the metadata. Beyond that, be sure you actually equip the PageRequest with sorting information to get stable results. Otherwise you might trigger the query twice and get different results even without the data having changed underneath.
If you are using Java 8 and Spring Data 1.7.0, you can use default methods if you want to combine a #Query annotation with setting maximum results:
public interface UserRepository extends PagingAndSortingRepository<User,Long> {
#Query("from User u where ...")
List<User> findAllUsersWhereFoo(#Param("foo") Foo foo, Pageable pageable);
default List<User> findTop10UsersWhereFoo(Foo foo) {
return findAllUsersWhereFoo(foo, new PageRequest(0,10));
}
}
There is a way you can provide the equivalent of "a setMaxResults(n) by annotation" like in the following:
public interface ISomething extends JpaRepository<XYZ, Long>
{
#Query("FROM XYZ a WHERE a.eventDateTime < :before ORDER BY a.eventDateTime DESC")
List<XYZ> findXYZRecords(#Param("before") Date before, Pageable pageable);
}
This should do the trick, when a pageable is sent as parameter.
For instance to fetch the first 10 records you need to set pageable to this value:
new PageRequest(0, 10)
Use Spring Data Evans (1.7.0 RELEASE)
the new release of Spring Data JPA with another list of modules together called Evans has the feature of using keywords Top20 and First to limit the query result,
so you could now write
List<User> findTop20ByLastname(String lastname, Sort sort);
or
List<User> findTop20ByLastnameOrderByIdDesc(String lastname);
or for a single result
List<User> findFirstByLastnameOrderByIdDesc(String lastname);
Best choice for me is native query:
#Query(value="SELECT * FROM users WHERE other_obj = ?1 LIMIT 1", nativeQuery = true)
User findByOhterObj(OtherObj otherObj);
new PageRequest(0,10) doesn't work in newer Spring versions (I am using 2.2.1.RELEASE). Basically, the constructor got an additional parameter as Sort type. Moreover, the constructor is protected so you have to either use one of its child classes or call its of static method:
PageRequest.of(0, 10, Sort.sort(User.class).by(User::getFirstName).ascending()))
You can also omit the use of Sort parameter and implicitly user the default sort (sort by pk, etc.):
PageRequest.of(0, 10)
Your function declaration should be something like this:
List<User> findByUsername(String username, Pageable pageable)
and the function will be:
userRepository.findByUsername("Abbas", PageRequest.of(0,10, Sort.sort(User.class).by(User::getLastName).ascending());
It's also posible using #QueryHints. Example bellow uses org.eclipse.persistence.config.QueryHints#JDBC_MAX_ROWS
#Query("SELECT u FROM User u WHERE .....")
#QueryHints(#QueryHint(name = JDBC_MAX_ROWS, value = "1"))
Voter findUser();
If your class #Repository extends JpaRepository you can use the example below.
int limited = 100;
Pageable pageable = new PageRequest(0,limited);
Page<Transaction> transactionsPage = transactionRepository.findAll(specification, pageable);
return transactionsPage.getContent();
getContent return a List<Transaction>.
Use
Pageable pageable = PageRequest.of(0,1);
Page<Transaction> transactionsPage = transactionRepository.findAll(specification, pageable);
return transactionsPage.getContent();