Generated sql not ok with criteriaBuilder function - spring-boot

I use spring boot 3
Specification<School> specification = (Root<School> root, CriteriaQuery<?> cq, CriteriaBuilder cb) -> {
Predicate p = cb.disjunction();
p.getExpressions().add(cb.function("contains", Boolean.class, root.get("adr"), cb.literal(search.adr())));
return p;
};
return findAll(specification, page);
Sql generated
from
school s1_0
where
1!=1
Edit same issue with Criteria
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<School> cq = cb.createQuery(School.class);
Root<School> rootSchool = cq.from(School.class);
Predicate p = cb.disjunction();
p.getExpressions().add(cb.function("contains", Boolean.class, rootSchool.get("adr"), cb.literal(adr)));
cq.where(p);
List<School> schools=entityManager.createQuery(cq).getResultList();

That's by design. Read the Java doc of Predicate#getExpressions. It says that changes to the list do not affect the predicate. You will have to build the predicate with CriteriaBuilder#or or simply build a predicate with CriteriaBuilder#isTrue and pass that to CriteriaQuery#where

Related

Criteria API deprecated, how to convert ti CriteriaQuery instead?

I am migrating an old existing project to Springboot.
I have following method:
public List<T> findAll(Class<?> clazz) {
Criteria search = entityManager.unwrap(Session.class).createCriteria(clazz);
List<T> entities = search.list();
return entities;
}
And another one:
public County findByName(String name) {
Criteria search = entityManager.unwrap(Session.class).createCriteria(County.class).add(Restrictions.eq("county", name));
County county = (County) search.uniqueResult();
return county;
}
This works but when i run the project I get following warning: Hibernate's legacy org.hibernate.Criteria API is deprecated; use the JPA javax.persistence.criteria.CriteriaQuery instead
How can i convert it to using javax.persistence.criteria.CriteriaQuery and still be able to receive the same result? I have never been using both of these APIs.
Thank for the help!
find all:
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Country> cq = cb.createQuery(Country.class);
Root<Country> rootCountry= cq.from(Country.class);
cq.select(rootCountry);
List<Country> result = entityManager.createQuery(cq).getResultList();
find by name:
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Country> cq = cb.createQuery(Country.class);
Root<Country> rootCountry= cq.from(Country.class);
cq.select(rootCountry);
cq.where(cb.equal(rootCountry.get(Country_.name),NAME))
// if you don't use metamodel (although it is recommended) -> cq.where(cb.equal(rootCountry.get("name"),NAME))
Country result = entityManager.createQuery(cq).getSingleResult();
Can you read more about this in this link https://docs.jboss.org/hibernate/entitymanager/3.5/reference/en/html/querycriteria.html

JPA CriteriaSepcification IN clause

I'm using SpringBoot 2.2.6 with JPA and I need to do query with IN clause as mentioned in Title. I have try with:
#Override
public Predicate toPredicate(Root<Distinta> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
List<Predicate> predicates = new ArrayList<>();
.....
.....
for (DistintaCriteria criteria : list) {
switch(criteria.getOperation()) {
case TEST:
Join<Entity, JoinEntity> join = root.join("joinEntity");
predicates.add(join.<Integer>get("id").in(criteria.getValue()));
}
}
where criteria.getValue() is a Integer[] array but it doesn't work. Can you help me?
Thank you all.
UPDATE
If I try the same Query with List<String> it works! With Integer I had this error:
Unaware how to convert value [[2, 3, 4, 5] : java.util.ArrayList] to requested type [java.lang.Integer]
I have solved as follows:
Join<Entity, JoinEntity> join = root.join("joinEntity");
Predicate in = join.get("id").in((List<Integer>)criteria.getValue());
predicates.add(in);
I don't know why with List<String> I don't need to cast.
Hope helps.
For in clause we need to pass a list always.
You need to convert your Integer array to Integer list using Java-8 like
List<Integer> values = Arrays.asList(criteria.getValue())
#Override
public Predicate toPredicate(Root<Distinta> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
List<Predicate> predicates = new ArrayList<>();
.....
.....
for (DistintaCriteria criteria : list) {
List<Integer> values = Arrays.asList(criteria.getValue());
switch(criteria.getOperation()) {
case TEST:
Join<Entity, JoinEntity> join = root.join("joinEntity");
predicates.add(join.<Integer>get("id").in(values));
}
}
In eclipse, we will get the warning like if we pass an array
Type Integer[] of the last argument to a method in(Object...) doesn't exactly match the vararg parameter type. Cast to Object[] to confirm the non-varargs invocation, or pass individual arguments of type Object for a varargs invocation.

CriteraQuery in toPredicate method is not working

I wanted to fetch only select column from the DB using multiple where clause below is my simple implementation. Am getting the result but am getting data for all the column instead of my requested fileName column alone.
List<ImageSiloVO> queryResult = imageSiloRepo.findAll(new Specification<ImageSiloVO>() {
#Override
public Predicate toPredicate(Root<ImageSiloVO> root, CriteriaQuery<?>query, CriteriaBuilder criteriaBuilder) {
query.select(root.get("fileName"));
List<Predicate> predicates = new ArrayList<>();
if(StringUtils.isNoneBlank(imageSiloVO.getEntryNumber())) {
predicates.add(criteriaBuilder.and(criteriaBuilder.equal(root.get("entryNumber"), imageSiloVO.getEntryNumber())));
}
return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
}
});

Specification OR clause, in and isEmpty/isNull

I have workers that have competences (driving licenses and such) and then there are mechanisms that require certain competences. Sometimes the mechanisms require no competences at all.
Currently I have a Specification with an in clause that works fine, but I would like it to also send out mechanisms that require no competences to operate.
public static Specification<Mechanism> hasCompetences(String searchTerm) {
return (root, query, criteriaBuilder) -> {
query.distinct(true);
List<String> list = new ArrayList<>(Arrays.asList(searchTerm.split(",")));
return root.join("competences").get("name").in(list);
};
}
If I have 3 mechanisms with competences like
Car | B-Category |
Van | C-Category |
Bicycle |(no data here) |
After requesting mechanisms?competences=B-Category it returns Car as expected, but I would like to get the Bicycle too.
Or is there a way to get all all mechanisms that don't require competences? I tried mechanisms?competences= but that returned [].
Edit:
This is where I'm at right now:
public static Specification<Mechanism> hasCompetences(List<String> list) {
return (root, query, cb) -> {
query.distinct(true);
return cb.or(
cb.isEmpty(root.join("competences")),
root.join("competences").get("name").in(list)
);
};
}
But the isEmpty is giving me this error:
java.lang.IllegalArgumentException: unknown collection expression type [org.hibernate.query.criteria.internal.path.SetAttributeJoin]
Edit2:
public static Specification<Mechanism> hasCompetences(List<String> list) {
return (root, query, cb) -> {
query.distinct(true);
Join<Mechanism, Set<Competence>> competences = root.join("competences", JoinType.LEFT);
return cb.or(
root.join("competences").get("name").in(list),
cb.isEmpty(competences)
);
};
}
Error:
unknown collection expression type [org.hibernate.query.criteria.internal.path.SetAttributeJoin];
You have 2 errors:
The criteria to match empty collection is cb.isEmpty(root.get("competences"))
You need to specify left join. root.join("competences", JoinType.LEFT)
Without the second amendment, you make an inner join, so you will never retrieve Mechanisms with empty competences.
Update
You proposed
Join<Mechanism, Set<Competence>> competences = root.join("competences", JoinType.LEFT);
return cb.or(
root.join("competences").get("name").in(list),
cb.isEmpty(competences)
);
isEmpty won't work on SetAttributeJoin (the result of root.join) - look point 1. above
Try
Join<Mechanism, Set<Competence>> competences = root.join("competences", JoinType.LEFT);
return cb.or(
competences.get("name").in(list),
cb.isEmpty(root.get("competences"))
);

Avoid duplicate code in criteria builder query in spring data jpa

I am using Spring Boot (1.5.14.RELEASE) and Spring data Jpa with Java 1.8. I want to avoid duplicate code.
Below query fetches employee details. It's working fine.
Class EmployeeDAO:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<EmployeeDto> cq = cb.createQuery(EmployeeDto.class);
Root<EmployeeInfo> root = cq.from(EmployeeInfo.class);
Join<EmployeeInfo, SalaryInfo> SalaryType = root.join("SalaryInfo");
Join<EmployeeInfo, CompanyInfo> Company = root.join("CompanyInfo");
cq.select(cb.construct(EmployeeDto.class,
root.get("FirstName"),
SalaryType.get("Salary"),
Company.get("CompanyName")))
.where(specification.toPredicate(root, cq, cb))
.orderBy(cb.asc(root.get("FirstName")));
Another function in same class in also making the almost 90% same criteria builder query as shown below:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<EmployeeDto> cq = cb.createQuery(EmployeeDto.class);
Root<EmployeeInfo> root = cq.from(EmployeeInfo.class);
Join<EmployeeInfo, SalaryInfo> SalaryType = root.join("SalaryInfo");
Join<EmployeeInfo, CompanyInfo> Company = root.join("CompanyInfo");
Join<EmployeeInfo, UserInfo> User = root.join("UserInfo");
cq.select(cb.construct(EmployeeDto.class,
root.get("FirstName"),
SalaryType.get("Salary"),
Company.get("CompanyName"),
User.get("Age")))
.where(specification.toPredicate(root, cq, cb))
.orderBy(cb.asc(root.get("FirstName")));
The code in both function is same except that below code is making join with UserInfo table to get user age. All other code is duplicate. Can you tell me how can I avoid this duplicate code.
Something like this:
public class EmployeeDAO {
EntityManager em;
Specification specification;
public void get(boolean withUsers) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<EmployeeDto> cq = cb.createQuery(EmployeeDto.class);
Root<EmployeeInfo> root = cq.from(EmployeeInfo.class);
Join<EmployeeInfo, SalaryInfo> salaryType = root.join("SalaryInfo");
Join<EmployeeInfo, CompanyInfo> company = root.join("CompanyInfo");
List<Selection> sels = new ArrayList<>();
Collections.addAll(sels,
root.get("FirstName"),
salaryType.get("Salary"),
company.get("CompanyName")
);
if (withUsers) {
Join<EmployeeInfo, UserInfo> user = root.join("UserInfo");
sels.add(user.get("Age"));
}
cq.select(cb.construct(EmployeeDto.class,
sels.toArray(new Selection[0])
))
.where(specification.toPredicate(root, cq, cb))
.orderBy(cb.asc(root.get("FirstName")));
}
}

Resources