CriteriaBuilder, specification and subquery - jpa

I use spring boot 3
School have a one to many relation with Teacher
I created 2 specfication
public Page<School> advanceSearch(SchoolSearch search, Pageable page) {
Specification<School> hasCityName = (Root<School> mainRoot, CriteriaQuery<?> mainCq, CriteriaBuilder mainCb) -> {
...
Predicate predicate..
return predicate;
};
Specification<Teacher> hasTeacherName = (Root<Teacher> mainRoot, CriteriaQuery<?> mainCq, CriteriaBuilder mainCb) -> {
...
Predicate predicate..
return predicate;
};
return findAll(hasCityName, page);
}
I search to do
select s from School s where s.city like city and
s.schoolId in
(select t.school.schooId from Teacher t where t.name like teacherNom)
how to do a in condition with specfication?
I tried with no success
Specification<Teacher> fromRepondant = (Root<Teacher> root, CriteriaQuery<?> cq, CriteriaBuilder cb) -> {
Predicate pre = cb.and(cb.equal(root.get("name"), search.name()));
Path<School> schooPath = root.get("school");
return cq.select(schooPath.get("schoolId")).from(School.class).in(pre);
};
Specification<School> hasIdIn = (Root<School> root, CriteriaQuery<?> cq, CriteriaBuilder cb) -> {
return cb.and(root.get("schooId").in(hasTeacherName));
};

Related

Add specification from a loop

In a spring boot 3 application I try to use specification
public Page<User> advancedSearch(UserSearch search, Pageable page) {
String[] splittedValues = search.name.split(" ");
Specification<User> hasPersonWithName = (Root<User> root, CriteriaQuery<?> cq, CriteriaBuilder cb) -> {
...
return pre;
};
return findAll(specification, page);
}
I need for every value in splittedValues to add a global specification
for (String splittedName: splittedValues) {
specification.or(splittedName);
}
and pass it to findAll
I don't understand how to do it
Edit
you solution seem to work but
that generate
where
1=1
or like e1_0.name "%bob%"
or like e1_0.name "%jame%"
It's there a way to get
where
1=1
and (
like e1_0.name "%bob%"
or like e1_0.name "%jame%"
)
Using or operator , you combine multiple conditions, checking whether the name of User contains one of the splittedValues. Predicate is then returned from specification.
I hope that was what you wanted, in case I didn't understand, please correct me. Here is an example if so, adapt to your own code.
Edit: To get 1=1 and (like e1_0.name "%bob%" or like e1_0.name "%jame%"), you can use cb.or method to combine the conditions, resulting nameConditions is then combined with pre using cb.and.
public Page<User> advancedSearch(UserSearch search, Pageable page) {
String[] splittedValues = search.name.split(" ");
Specification<User> hasPersonWithName = (Root<User> root, CriteriaQuery<?> cq, CriteriaBuilder cb) -> {
Predicate pre = cb.conjunction();
Predicate nameConditions = null;
for (String splittedName: splittedValues) {
if (nameConditions == null) {
nameConditions = cb.like(root.get("name"), "%" + splittedName + "%");
} else {
nameConditions = cb.or(nameConditions, cb.like(root.get("name"), "%" + splittedName + "%"));
}
}
if (nameConditions != null) {
pre = cb.and(pre, nameConditions);
}
return pre;
};
return findAll(hasPersonWithName, page);
}

Dynamic JPQL query qith JOIN

Had to write a jpql query, based on the input need to add and condition and for some input had to need JOIN queries.
#Override
public List<IncidentHdr> fetchIncidents(IncidentHdrDto incidentHdrDto) {
StringBuilder query = new StringBuilder();
query.append(ReposJPQL.GET_INCIDENT_DETAILS);
Map<String, Object> parameters = new HashMap<String, Object>();
List<String> criteria = new ArrayList<String>();
if(incidentHdrDto.getIncidentId() > 0) {
criteria.add("inc.incidentId = :incidentId");
parameters.put("incidentId", incidentHdrDto.getIncidentId());
}
if(incidentHdrDto.getCatCode() > 0) {
criteria.add("inc.catCode = :catCode");
parameters.put("catCode", incidentHdrDto.getCatCode());
}
if(incidentHdrDto.getType != null) {
//here i need to generate a join query
//SELECT * FROM INCIDENT JOIN CATEGORY_MAST ON(INCIDENT.CAT_CODE = CATEGORY_MAST.CAT_CODE) WHERE CATEGORY_MAST.TYPE_CODE = 16
}
Query q = em.createQuery(query.toString());
logger.info("Get Incidents Query : "+query.toString());
for (Entry<String, Object> entry : parameters.entrySet()) {
q.setParameter(entry.getKey(), entry.getValue());
}
List<IncidentHdr> incidentHdrs = q.getResultList();
return incidentHdrs;
}
where as ReposJPQL is the base query which had a where condition.
public interface ReposJPQL {
public String GET_INCIDENT_DETAILS = "SELECT inc FROM IncidentHdr inc WHERE 1 = 1" ;
}

JPA findAll(spec,Sort)

I have this code to get all transaction between 2 dates. I would like to get a desc sorted list. What are the possibilities?
#Override
public List<Transaction> searchBySubmitDate(final Date startDate,
final Date endDate) {
return transactionRepository.findAll(new Specification<Transaction>() {
#Override
public Predicate toPredicate(Root<Transaction> transaction,
CriteriaQuery<?> q, CriteriaBuilder cb) {
Predicate between = cb.between(transaction.get(Transaction_.dateSubmit), startDate, endDate);
return between;
}
});
#Override
public List<Transaction> searchBySubmitDate(final Date startDate,
final Date endDate) {
return transactionRepository.findAll(new Specification<Transaction>() {
#Override
public Predicate toPredicate(Root<Transaction> transaction,
CriteriaQuery<?> q, CriteriaBuilder cb) {
Predicate between = cb.between(transaction.get(Transaction_.dateSubmit), startDate, endDate);
return between;
}
},new Sort(Direction.DESC,"dateSubmit"));
The repository has another method taking a Sort as additional argument. Call this method with an appropriate Sort instance.
I thought I would leave a more up-to-date answer, since it's been 7 years. This is how we do it now:
interface TransactionRepository extends Repository<Transaction, Long> {
#Query(value = "SELECT * FROM Transaction AS t " +
"WHERE t.date >= :since AND t.date <= :until", nativeQuery = true)
public List<Transaction> findTransactionBetween(#Param("since" String since,
#Param("until" String until);
}
You can read more in the Spring JPA documentation

Count JPA and Invalid Path

After poking around Stack Overflow I found the following solution for counting problem. My requirement is to get the total number of matching rows, and return the first ten for pagination purposes.
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<T> cq = cb.createQuery(clazz);
CriteriaQuery<Long> counterCq = cb.createQuery(Long.class);
counterCq.select(cb.count(counterCq.from(clazz)));
Predicate predicate= null;
Predicate predicate1 = null;
Root<T> root = cq.from(clazz);
for (Map.Entry<String, String> e : filters.entrySet()){
predicate = cb.and(cb.like(root.<String>get(e.getKey()), e.getValue()+ "%"));
}
if(predicate != null){
cq.where(predicate);
counterCq.where(predicate);
}
int pn = ( em.createQuery(counterCq).getSingleResult()).intValue();
logger.debug("number of pages is {}", pn);
setRowCount(pn);
if(sortField !=null && !sortField.trim().equals("")){
if(sortOrder == SortOrder.DESCENDING){
cq.orderBy(cb.desc(root.get(sortField)));
} else{
cq.orderBy(cb.asc(root.get(sortField)));
}
}
Query q = em.createQuery(cq);
q.setFirstResult(first);
q.setMaxResults(first+ps);
List<T> cats= (List<T>)q.getResultList();
This snippet makes hibernate to through
java.lang.IllegalArgumentException: org.hibernate.hql.ast.QuerySyntaxException: Invalid path: 'generatedAlias1.title' [select count(generatedAlias0) from Media as generatedAlias0 where generatedAlias1.title like :param0]
It seems like cq.from(clazz) cannot be applied for the other query.
Now my question: Is there a way to use the same predicate in both queries?
Your predicate list isn't assembled correctly. You have to 'and' predicates together into a single expression. I also prefer to build my predicate before performing the select for better readability.
Here's a refactor of your code to achieve the correct results:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<T> cq = cb.createQuery(clazz);
Root<T> root = cq.from(clazz);
// build predicate list - conjuction starts us with an empty 'and' predicate
Predicate predicate = cb.conjunction();
for (Map.Entry<String, String> e : filters.entrySet()) {
predicate = cb.and(predicate, cb.like(root.get(e.getKey()), e.getValue() + "%"));
}
// query total count
CriteriaQuery<Long> counterCq = cb.createQuery(Long.class);
counterCq.select(cb.count(root)).where(predicate);
int pn = (em.createQuery(counterCq).getSingleResult()).intValue();
logger.debug("number of pages is {}", pn);
setRowCount(pn);
// query results
cq.select(root).where(predicate);
if(sortField !=null && !sortField.trim().equals("")) {
if(sortOrder == SortOrder.DESCENDING) {
cq.orderBy(cb.desc(root.get(sortField)));
}
else {
cq.orderBy(cb.asc(root.get(sortField)));
}
}
TypedQuery<T> q = em.createQuery(cq);
q.setFirstResult(first);
q.setMaxResults(first+ps);
List<T> list = q.getResultList();

how could i use JPA criteria query api for joined columns?

i am new to JPA and i have a problem with it.
suppose that we have two tables which are related
by a ManytoOne association, which means that
table A stores a primary key of table B within it.
when these two tables are mapped to JPA entities
i have a problem for search on this situation.
i have used an existing code from richfaces demo, to handle filtering and sorting by using
JPA. this code is using input parameters to create criteria query.
this is the code:
private CriteriaQuery<T> createSelectCriteriaQuery() {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<T> criteriaQuery = criteriaBuilder.createQuery(entityClass);
Root<T> root = criteriaQuery.from(entityClass);
if (arrangeableState != null) {
List<Order> orders = createOrders(criteriaBuilder, root);
if (!orders.isEmpty()) {
criteriaQuery.orderBy(orders);
}
Expression<Boolean> filterCriteria = createFilterCriteria(criteriaBuilder, root);
if (filterCriteria != null) {
criteriaQuery.where(filterCriteria);
}
}
return criteriaQuery;
}
protected Expression<Boolean> createFilterCriteriaForField(String propertyName, Object filterValue, Root<T> root, CriteriaBuilder criteriaBuilder) {
String stringFilterValue = (String) filterValue;
if (Strings.isNullOrEmpty(stringFilterValue)) {
return null;
}
stringFilterValue = stringFilterValue.toLowerCase(arrangeableState.getLocale());
Path<String> expression = root.get(propertyName);
Expression<Integer> locator = criteriaBuilder.locate(criteriaBuilder.lower(expression), stringFilterValue, 1);
return criteriaBuilder.gt(locator, 0);
}
private Expression<Boolean> createFilterCriteria(CriteriaBuilder criteriaBuilder, Root<T> root) {
Expression<Boolean> filterCriteria = null;
List<FilterField> filterFields = arrangeableState.getFilterFields();
if (filterFields != null && !filterFields.isEmpty()) {
FacesContext facesContext = FacesContext.getCurrentInstance();
for (FilterField filterField : filterFields) {
String propertyName = (String) filterField.getFilterExpression().getValue(facesContext.getELContext());
Object filterValue = filterField.getFilterValue();
Expression<Boolean> predicate = createFilterCriteriaForField(propertyName, filterValue, root, criteriaBuilder);
if (predicate == null) {
continue;
}
if (filterCriteria == null) {
filterCriteria = predicate.as(Boolean.class);
} else {
filterCriteria = criteriaBuilder.and(filterCriteria, predicate.as(Boolean.class));
}
}
}
return filterCriteria;
}
the code is okay, when i try to filter columns(not joined columns), but when i try to
query on joined column, the produced query is not correct and it throws exception.
so my question is that, how could i use JPA criteria query api, to filter rows by both
joined columns and non-joined coulmns.
thanks
I don't believe you can treat join columns like regular ones.
for example if you want to filter on id of B, you would have to create a join from A to B , then use B_.id to match values.
Shay