Spring Data JPA - #Query with null values based on user input - spring

The user has a form:
checkbox with fields status and priority
submit btn
Objective: Query DB based on these values.
If one of them is null or false, the criteria should be ignored.
Ex: status is checked and priority is not, I want to query only based on status.
The code below will never execute with one of them being false. I also read about Query by Example but could not find a solution.
Null values can indeed be ignored, but need to be previously defined.
I thought about replacing null with something similar to *.
Ex: WHERE c.status = *. It didn't work.
#Query(value = "SELECT * FROM tickets c WHERE c.status = :status AND c.priority= :priority",
nativeQuery = true)
List<Ticket> findByFilter(#Param("status") String status,
#Param("priority") String priority);
Do you have any idea how can I do this?
Thank you

Thank you Simon for pointing me in the right direction.
This was the web page that helped me:
https://dimitr.im/writing-dynamic-queries-with-spring-data-jpa
My situation:
Created a specification class:
public final class DbFilterSpecification {
public static Specification<Ticket> statusContains(String expression) {
return (root, query, builder) -> builder.like(root.get("status"), contains(expression));
}
public static Specification<Ticket> priorityContains(String expression) {
return (root, query, builder) -> builder.like(root.get("priority"), contains(expression));
}
private static String contains(String expression) {
return MessageFormat.format("%{0}%", expression);
}
}
Created a method inside the service layer:
public List<Ticket> findAllWithSpecification(String status, String priority) {
Specification<Ticket> specification = Specification
.where(status == null ? null : DbFilterSpecification.statusContains(status))
.and(priority == null ? null : DbFilterSpecification.priorityContains(priority));
return ticketRepository.findAll(specification);
}

Related

if/else doese not work in my POST Method of SpringBoot rest api

I use Spring Boot rest api with MongoDB.
In the POST Method, if there is not scoreID and there is not a player with specific date in my collection, because at the same time a player cannot play different games and bring score, then if the specific player and gamecode exist, create a score.
In fact, in the POST Method I used Nested IF-ELSE conditions.
But, in the Postman when I execute POST Request with this data:
{
"scoreid":"s11",
"score":1000,
"player":"sahari",
"gamecode":"g12",
"date":"2020-01-01"
}
always, I recieve an error, in the Postman, 400 Bad Request!, which i defined in the last line of my IF-ELSE statements.
I do not know, what is my mistake and why my program doese not execute IF conditions correct.
The POST Method:
//Create Score
#PostMapping
public ResponseEntity<?> createScore(#RequestBody #JsonView(Views.class) #Valid Score score) {
String p = srepo.findByPlayerName(score.getPlayer());
String g = srepo.findByGameCode(score.getGamecode());
String scoreid = srepo.findByScoreid(score.getScoreid());
Query query = new Query();
query.addCriteria(new Criteria().andOperator(Criteria.where("player").is(score.getPlayer()),
Criteria.where("date").is(score.getDate())));
if((scoreid != null)) {
return ResponseEntity.status(409).body("Conflict!"); }
else
if(mongoTemplate.exists(query, Score.class))
return ResponseEntity.status(409).body("There is not Possible at same time one player brings different Scores!");
else
if((p!= null)&&(g!= null))
{
history = new ArrayList<History>();
h = new History();
h.setScore(score.getScore());
h.setDate(score.getDate());
history.add(h);
hrepo.save(h);
score.setHistory(history);
srepo.insert(score);
return ResponseEntity.ok(score);
}
else
{
return ResponseEntity.status(400).body("Bad Request!");
}
}
The Score Repository:
#Repository
public interface ScoreRepository extends MongoRepository<Score, String>{
#Query("{'scoreid':?0}")
public String findByScoreid(String scoreid);
#Query("{'Player.nickname':?0}")
public String findByPlayerName(String player);
#Query("{'Games.code':?0}")
public String findByGameCode(String game);
}
The problem is not for my IF-ELSE statements.The problem is in the Score Repository
I must return a List instead of String for findByPlayerName and findByGameCode and for findByScoreid which is for checking duplicate in the POST Method I must return type of Score

Cannot use MatchMode with variable of type double

I am implementing server-side filtering of dataTable. In the service which implements the filtering I want to test if the search criteria is a number ; if so then I implement the filtering based on the bean attribute of type double ( salary ) , otherwise I make the filtering based on the bean attribute of type String ( username ) :
#Override
#Transactional
public List<User> list(int start, int length, String search) {
Criteria criteres = sessionFactory.getCurrentSession().createCriteria(User.class);
if (!search.equals("")) {
if (NumberUtils.isNumber(search))
criteres.add(Restrictions.like("salary", Double.parseDouble(search)));
else
criteres.add(Restrictions.like("username", search, MatchMode.ANYWHERE));
}
criteres.setFirstResult(start);
criteres.setMaxResults(length);
criteres.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
#SuppressWarnings("unchecked")
List<User> listUser = (List<User>) criteres.list();
return listUser;
}
The problem is that if I write :
if (!search.equals("")) {
if (NumberUtils.isNumber(search))
criteres.add(Restrictions.like("salary", String.valueOf(Double.parseDouble(search)), MatchMode.ANYWHERE));
else
criteres.add(Restrictions.like("username", search, MatchMode.ANYWHERE));
}
then I got alert error : DataTables warning: table id=t_list - Ajax error. For more information about this error, please see http://datatables.net/tn/7
So how to make possible to make a "like" condition on the double variable ?

Spring repository filter by using an object

So I'm making use of PaginAndSortingRepository<> in Spring. Now I have an object that is bound to this repository. Let's call it AuditLogEntry. So my becomes PagingAndSortingRepository<AuditLogEntry, Long>. I need to apply some filters to this repository, so I created a new model called AuditLogEntryFilter. This is not bound to the AuditLogEntry itself, but I want to use this object to filter out my AuditLogEntry's. The first approach that I've came up with myself is make methods for each of the filter's properties that can be different. This becomes very complicated when the filter' size is expanding. The thing I want to do is this:
List<AuditLogEntry> filterByAuditLogEntryFilter(AuditLogEntryFilter filter);
Does anybody has an idea how to accomplish this? I already looked into make an implementation of the PagingAndSortingRepository, but then I have to implement all of the other methods. Which I don't want to do.
Update solution:
Thanks to #Nikolay Rusev, I come up with the following solution:
In my controller, I call my repository like this:
#RequestMapping(value = "/auditentries", method = RequestMethod.POST)
public String processFilter(final AuditLogEntryFilter filter, BindingResult bindingResult, ModelMap
modelMap, Pageable pageable) {
Page<AuditLogEntry> auditLogEntryPage = auditLogEntryRepository.findAll(auditLogEntryFilterSpec(filter), pageable);
modelMap.addAttribute("filter", filter);
return "audit";
}
Then I made a spec with roughly the following code:
public static Specification<AuditLogEntry> auditLogEntryFilterSpec(AuditLogEntryFilter filter) {
return new Specification<AuditLogEntry>() {
#Override
public Predicate toPredicate(Root<AuditLogEntry> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
List<Predicate> predicateList = new ArrayList<Predicate>();
if(filter.getDateFrom() != null || filter.getDateTo() != null) {
if(filter.getDateFrom() != null && filter.getDateTo() == null)
{
Predicate auditLogEntryFromDatePredicate = criteriaBuilder.greaterThan(root.get("timestamp"), filter.getDateFrom());
predicateList.add(auditLogEntryFromDatePredicate);
}
if(filter.getDateFrom() == null && filter.getDateTo() != null) {
Predicate auditLogEntryToDatePredicate = criteriaBuilder.lessThan(root.get("timestamp"), filter.getDateTo());
predicateList.add(auditLogEntryToDatePredicate);
}
if(filter.getDateFrom() != null && filter.getDateTo() != null) {
Predicate auditLogEntryFromDatePredicate = criteriaBuilder.greaterThan(root.get("timestamp"), filter.getDateFrom());
Predicate auditLogEntryToDatePredicate = criteriaBuilder.lessThan(root.get("timestamp"), filter.getDateTo());
predicateList.add(auditLogEntryFromDatePredicate);
predicateList.add(auditLogEntryToDatePredicate);
}
}
Predicate[] predicates = new Predicate[predicateList.size()];
predicateList.toArray(predicates);
return criteriaBuilder.and(predicates);
}
};
}
Now it retrieves the right results according to the filters.
May be you need to look at spring data specifications. here is the spring documentation: http://docs.spring.io/spring-data/jpa/docs/1.5.0.RELEASE/reference/html/jpa.repositories.html#specifications
here is a good articles about it:
http://info.michael-simons.eu/2014/09/24/creating-specification-instances-for-spring-data-jpa-with-spring-mvc/
http://blog.kaczmarzyk.net/2014/03/23/alternative-api-for-filtering-data-with-spring-mvc-and-spring-data/
hope it helps

How to set a Http Session parameter in a JPA named query

I want to set a parameter in a named query (JPA 2.0), so my dataTable would render the respective dataSet. The parameter is obtained remotely and injected in a AbstractFacade class.
I've tried to achieve this through the code above, but it's not working.
Can someone help me?
AbstractFacade (main code):
private String prefDep;
public List<T> findByPrefDep() {
prefDep= FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("xPrefDep");
javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
cq.select(cq.from(entityClass));
return getEntityManager().createQuery(cq).setParameter("prefDep", prefDep).getResultList();
}
The Entity class (main code):
#NamedQuery(name = "Capacitacao.findByPrefDep", query = "SELECT c FROM Capacitacao c WHERE c.prefDep = :prefDep"),
The AbstractController:
public Collection<T> getItems() {
if (items == null) {
items = this.ejbFacade.findByPrefDep();
}
return items;
}
There is no exception launched, but the dataSet rendered corresponds to a findAll named query.
Thanks in advance.
Your code doesn't use your named query at all. A named query has a name, and your code doesn't use that name anywhere.
Use
getEntityManager().createNamedQuery("Capacitacao.findByPrefDep", Capacitacao.class)
.setParameter("prefDep", prefDep)
.getResultList();
You could have found that yourself by simply reading the EntityManager javadoc.

CriteriaBuilder - Sum using SelectCase

I am trying to perform a summation SQL query like the following:
select group_ID, sum(case when user_type = 'Exec' then 1000
when user_type = 'Office' then 10 else 0 end)
from subscription
group by group_ID;
using the following snippet from a hiberate CriteriaBuilder query:
criteriaBuilder.sum(
criteriaBuilder.selectCase()
.when(criteriaBuilder.equal(subscriptionJoin.get(Subscription_.userType), "Exec"),1000)
.when(criteriaBuilder.equal(subscriptionJoin.get(Subscription_.userType), "Office"),1)
.otherwise(101))
However the following compile error appears:
Inferred type 'java.lang.object' for type parameter 'N' is not within its bound; should extend 'java.lang.number'
Any idea how to support performing a summation using the selectCase?
Sum is defined as follows:
<N extends Number> Expression<N> sum(Expression<N> x);
So reason to the compilation error is that sum method expect such arguments which is Expression with type that extends Number. It determines type from the selectCase and ends up with java.lang.Object, which is not acceptable.
Problem can be solved by giving type parameter (<Number>):
criteriaBuilder.sum(
criteriaBuilder.<Number>selectCase()
We are using Spring Data JPA in our project and i have the same case where i need to do sum. Instead of criteria query i'm just following the "named parameters" approach because this approach seems easy.
My method which gives me sum is as follows.
public interface ITransactionEntryRepo extends PagingAndSortingRepository<TransactionEntryEntity, String> {
#Query("select SUM(CASE WHEN te.debit = 'Y' THEN (te.amount * - 1) WHEN te.debit = 'N' THEN te.amount ELSE 0 END) AS availablebalance FROM TransactionEntity t, TransactionEntryEntity te WHERE t.id = te.transactionEntity.id and te.accountEntity.id = :id and te.valid = 'T' and t.retcode = 'XX' GROUP BY te.accountEntity.id")
public double findAvailableBalance(#Param("id") String id);
}
And I call this method in the class where i need
double balance = iTransactionEntryRepo.findAvailableBalance(accountEntity.getId());
and pass it(balance) wherever I need to. Hope this helps someone.
For aggregate operation you should pass the CriteriaQuery with numeric type to be proper expression for criteria builder, however this may not affect your criteria base restriction of you entity type. Finally you can append the desired predicates to your criteria query for having criteria base aggregation.
public class Aggregate<T, S extends Number> {
public Aggregate(Class<T> tableType, Class<S> type) {
this.criteriaBuilder = entityManager.getCriteriaBuilder();
this.criteria = criteriaBuilder.createQuery(type);
this.root = criteria.from(tableType);
}
public Aggregate<T, S> aggregate(String field) {
criteria.select(criteriaBuilder.sum(root.get(field)));
return this;
}
public <I> Aggregate<T, S> restrict(String field, I i) {
criteria.where(criteriaBuilder.equal(root.get(field), i));
return this;
}
public S perform() {
return entityManager.createQuery(criteria).getSingleResult();
}
private Root<T> root;
private final CriteriaQuery<S> criteria;
private final CriteriaBuilder criteriaBuilder;
}

Resources