How to create custom query using Spring Data JPA Specifications? [closed] - java

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 3 months ago.
Improve this question
In my Spring Boot app, I created a custom filtering using JPA Specification as mentioned on Searching And Filtering Using JPA Specification - Spring Boot. However, I need to join multiple tables and build a WHERE clause for my specific search via #Query.
I checked https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#specifications page, but could not build a proper structure.
So, how can create a dynamic WHERE clause for my query?

You can use Specification. To create:
Specification<Entity> spec = (root, query, cb) -> {
List<Predicate> predicates = new ArrayList<>();
// Add conditions to the predicates list
return cb.and(predicates.toArray(new Predicate[predicates.size()]));
};
The predicates list is used to hold conditions for the WHERE. You can add conditions to this list using the cb (CriteriaBuilder) object and the root and query parameters. These parameters provide access to the entity and the query being constructed.
You can then use the Specification object in a #Query annotation on your repository method to apply the dynamic WHERE clause to the query.
Repository example:
#Repository
public interface EntityRepository extends JpaRepository<Entity, Long>, JpaSpecificationExecutor<Entity> {
// Other repository methods
List<Entity> findAll(Specification<Entity> spec, Pageable pageable);
}
The above repository extends the JpaSpecificationExecutor to allow working with the JPA criteria API. The findByFields method also takes a Specification object as an argument. This Specification dynamically constructs the WHERE clause for the query.
So running the query:
List<Entity> entities = entityRepository.finAll(spec, pageable);

It's something like this:
Specification<BugData> bugDataSpecification = new Specification<BugData>() {
#Override
public Predicate toPredicate(Root<BugData> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
Path<Object> bugName = root.get("bugName");
Path<Object> bugType = root.get("bugType");
Path<Object> bugLevel = root.get("bugLevel");
List<Predicate> predicateListAnd = new ArrayList<>();
List<Predicate> predicateListOr = new ArrayList<>();
if (!StringUtils.isNullOrEmpty(bugRequestParam.getBugLevel())) {
Predicate pLevel = cb.equal(bugLevel, bugRequestParam.getBugLevel()); // ==
predicateListAnd.add(pLevel);
}
for (int i = 0; i < bugRequestParam.getBugTypeList().size(); i++) {
Predicate p1 = cb.equal(bugType, bugRequestParam.getBugTypeList().get(i));
predicateListOr.add(p1);
}
if (!StringUtils.isNullOrEmpty(bugRequestParam.getBugName())) {
Expression<Integer> findStr = cb.locate(bugName.as(String.class), bugRequestParam.getBugName()); //LOCATE
Predicate pName = cb.greaterThan(findStr, 0); // >
predicateListAnd.add(pName);
}
Predicate resultAnd[] = predicateListAnd.toArray(new Predicate[predicateListAnd.size()]);
Predicate resultOr[] = predicateListOr.toArray(new Predicate[predicateListOr.size()]);
Predicate end = cb.and(cb.and(resultAnd), cb.or(resultOr));
return end;
}
};
The whole part of this code:
#GetMapping(value = "specification")
public List<BugData> whereTiaojian() {
BugRequestParam bugRequestParam = new BugRequestParam();
bugRequestParam.setBugLevel("mid");
bugRequestParam.setBugName("CVE-2019-8331");
bugRequestParam.setLimit(100);
bugRequestParam.setPage(0);
List<String> bugTypeList = new ArrayList<>(4);
bugTypeList.add("CWE-79");
bugTypeList.add("CWE-502");
bugTypeList.add("CWE-284");
bugRequestParam.setBugTypeList(bugTypeList);
Pageable pageable = PageRequest.of(bugRequestParam.getPage(), bugRequestParam.getLimit());
Specification<BugData> bugDataSpecification = new Specification<BugData>() {
#Override
public Predicate toPredicate(Root<BugData> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
Path<Object> bugName = root.get("bugName");
Path<Object> bugType = root.get("bugType");
Path<Object> bugLevel = root.get("bugLevel");
List<Predicate> predicateListAnd = new ArrayList<>();
List<Predicate> predicateListOr = new ArrayList<>();
if (!StringUtils.isNullOrEmpty(bugRequestParam.getBugLevel())) {
Predicate pLevel = cb.equal(bugLevel, bugRequestParam.getBugLevel());
predicateListAnd.add(pLevel);
}
for (int i = 0; i < bugRequestParam.getBugTypeList().size(); i++) {
Predicate p1 = cb.equal(bugType, bugRequestParam.getBugTypeList().get(i));
predicateListOr.add(p1);
}
if (!StringUtils.isNullOrEmpty(bugRequestParam.getBugName())) {
Expression<Integer> findStr = cb.locate(bugName.as(String.class), bugRequestParam.getBugName());
Predicate pName = cb.greaterThan(findStr, 0);
predicateListAnd.add(pName);
}
Predicate resultAnd[] = predicateListAnd.toArray(new Predicate[predicateListAnd.size()]);
Predicate resultOr[] = predicateListOr.toArray(new Predicate[predicateListOr.size()]);
Predicate end = cb.and(cb.and(resultAnd), cb.or(resultOr));
return end;
}
};
Page<BugData> bugDataPage = bugDataVersionFiveDao.findAll(bugDataSpecification, pageable);
// This findAll method is the most important part of this all;
return bugDataPage.getContent();
}

Related

Java Spring Data : Sort By ID Desc

I just want my Data to be sorted by ID Descendent and i don't know how to do and this is my code in service layer
public Page<Facture> selectByPage(Pageable p) {
Page<Facture> pagedResult = factureRepository.findAll(p);
return pagedResult;
}
You can use Sort.of(...).descending() to sort by fields in descending order.
public Page<Facture> selectByPage(Pageable p) {
// Here, replace "id" with your field name that refers to id.
Pageable pSort = PageRequest.of(p.getPageNumber(), p.getPageSize(), Sort.by("id").descending());
Page<Facture> pagedResult = factureRepository.findAll(pSort);
return pagedResult;
}
A simple example with Specification with data JPA:
Sort sort = Sort.by(Sort.Direction.DESC, "id");
Pageable pageable = PageRequest.of(pageNo, pageSize, sort);
Specification specification = simpleSearchSpecification(bcpConsumerVo, bcpUserVo);
Page<BcpConsumer> bcpConsumers = bcpConsumerRepository.findAll(specification, pageable);
simpleSearchSpecification method:
private Specification simpleSearchSpecification(BcpConsumerVo bcpConsumerVo, BcpUserVo bcpUserVo) {
return (Specification<BcpConsumer>) (root, criteriaQuery, criteriaBuilder) -> {
List<Predicate> predicateList = new ArrayList<>(6);
List<Predicate> orList = new ArrayList<>();
if (Boolean.FALSE.equals(bcpUserVo.isAdmin())) {
predicateList.add(criteriaBuilder.equal(root.get("owner"), bcpUserVo.getId()));
}
if (!StringUtils.isEmpty(bcpConsumerVo.getName())) {
orList.add(criteriaBuilder.like(root.get("name"), "%" + bcpConsumerVo.getName() + "%"));
orList.add(criteriaBuilder.like(root.get("authKey"), "%" + bcpConsumerVo.getName() + "%"));
}
predicateList.add(criteriaBuilder.or(orList.toArray(new Predicate[0])));
return criteriaBuilder.and(predicateList.toArray(new Predicate[0]));
};
}

Reversed "in" list clause JPA criteria in Hibernate

I develop my project web application and I wanted to change searching profiles feature to use JPA Criteria. My idea is to ommit these searching criteria, which fields in html form were left blank. The difficult part is to write Predicate with String List of interests.
public List<Profile> searchProfiles(String sex, String city, List<String> interests) {
List<String> emptyInterests = new ArrayList<>(); emptyInterests.add("");
Session session = this.sessionFactory.getCurrentSession();
CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaQuery<Profile> criteriaQuery = builder.createQuery(Profile.class);
Root<Profile> root = criteriaQuery.from(Profile.class);
List<Predicate> predicates = new ArrayList<>();
if(!"".equals(sex)) {
predicates.add(builder.equal(root.get("sex"), sex ));
}
if(!"".equals(city)) {
predicates.add(builder.equal(root.get("city"), city ));
}
if(!emptyInterests.equals(interests)) {
// REASON OF ASKING THIS QUESTION
}
criteriaQuery.select(root).where(predicates.toArray(new Predicate[]{}));
return session.createQuery(criteriaQuery).list();
}
In the last "if" block I want to add Predicate which will means more or less "add Profile to results list if its String list of interests (Profile class field) contains all elements from method argument "interests" ". This condition in normal list filtering it would look like:
for(Profile profile : profiles) {
if(profile.getInterests().contains(interests))
results.add(profile);
}
Edit:
Following code causes ClassCastException: java.base/java.lang.String cannot be cast to java.base/java.util.List, in the return line.
if(!emptyInterests.equals(interests))
{
Expression<String> interestsExpression = root.get("interests");
Predicate interestsPredicate = interestsExpression.in(interests);
predicates.add(interestsPredicate);
}

How parse a type String to Long in Criteria with JPA using expression LIKE?

I'm new here, I have a problem writting a method where I tried to call a expression with criteria using JPA and Spring, I have the next code:
#Override
public List<ContractOrder> getOrdersByIn(List<String> paramsIn ) {
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<ContractOrder> query = builder.createQuery(ContractOrder.class);
Root<ContractOrder> root = query.from(ContractOrder.class);
Expression<String> exp = root.get("**order_id**");
Predicate predicateIn = exp.**in**(paramsIn);
ParameterExpression<Long> pexp = builder.parameter(Long.class,"order_id");
Predicate predicateLike = builder.like(exp, pexp);
query.where(builder.or(predicateIn,predicateLike));
TypedQuery<ContractOrder> queryT = entityManager.createQuery(query.select(root));
queryT.setParameter(0, Long.valueOf("%5"));
List<ContractOrder> lista = queryT.getResultList();
return lista;
}
Where "order_id" is mapping as type long and I want to pass a parameter like this "%5".
Can you help me with it?
Regards!!
Why this %5 ???
It's a variable ???????
queryT.setParameter(0, Long.valueOf(5));
or
queryT.setParameter(0, Long.valueOf("5"));
should be OK.
Why ** and not just * ?I don't understand...
Predicate predicateIn = exp.**in**(paramsIn);
Is it compile?????

JPA Criteria API in with two lists

I've been searching for a while and I don't find what i want...
Here is my code :
public List<MasseSalariale> findMasseSalarialeByCriteria(List<Section> sections,
Integer exercice, List<BpCaEtats> etats) {
List<String> etatString = new ArrayList<String>();
for (BpCaEtats e : etats) {
etatString.add(BpCaEtats.bpCaEtatToString(e));
}
CriteriaBuilder builder = this.getCriteriaBuilder();
CriteriaQuery<MasseSalariale> query = builder.createQuery(MasseSalariale.class);
Root<MasseSalariale> masseSalariale = query.from(MasseSalariale.class);
Join<MasseSalariale, Etablissement> etablissement =
masseSalariale.join(MasseSalariale_.etablissement);
Predicate p1 = builder.equal(masseSalariale.get(MasseSalariale_.annee), exercice);
Expression<List<Section>> exp2 = etablissement.get(Etablissement_.sections);
Predicate p2 = exp2.in(sections);
Expression<String> exp3 = masseSalariale.get(MasseSalariale_.etat);
Predicate p3 = exp3.in(etatString);
query.select(masseSalariale).where(builder.and(p1, p2, p3));
return this.find(query);
}
Basically, I need to know if one (at least) value in the section list from etablissement is contained in the section list in parameter. But the predicate p2 is wrong I think...
Using ListJoin, it worked well :
ListJoin<Etablissement, Section> sectionsEtab = etablissement.join(Etablissement_.sections);
Predicate p2 = sectionsEtab.in(sections);
Thanks

building a criteria query with jpa 2.0 by using a dynamic list

I'm a bit confused while creating a criteriaQuery with JPA 2.0.
Prerequisites:
I have a Gui, where the user can mark some checkboxes of (let us say) wheatherstations with some options like temperature/wind/timeperiod/etc...
Now I want to set up a criteriaQuery to pick just the selected items from a sql database and return it as an object/Map/List for building some DataModels (this will be used for generating a few primefaces charts).
What i have so far:
// for presentation purposes just this mockup-data
Calendar start = new GregorianCalendar(2011, Calendar.APRIL, 1);
Calendar end = new GregorianCalendar(2011, Calendar.MAY, 1);
List<String> selectedStations = new LinkedList<String>() {{
add("PS1");
add("PS2");
add("PS3");
}};
Map<String, Object selectedOptions = new LinkedHashMap<String, Object>() {{
put("opt1","val1");
put("opt2","val2");
put("opt3","val3");
}};
List<String> sel = new LinkedList<String>() {{
add("selOpt1");
add("selOpt2");
add("selOpt3");
}};
criteriaBuilder, criteriaQuery and the mapping class:
// go for the criteriaBuilder
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Tuple> cq = cb.createTupleQuery();
Root<StationItem> r = cq.from(StationItem.class);
Setting up the predicates:
// ... where (name="PS1" or name="PS2" or name="PS3") ...
Predicate p1 = cb.disjunction();
for (String s : selectedStations) {
p1 = cb.or(p1, cb.equal(r.get("name").as(String.class), s));
}
Predicate p2 = cb.between(r.get("fetchDate").as(Date.class),
start.getTime(), end.getTime());
Predicate p3 = cb.conjunction();
for (Map.Entry<String, Object> param : selectedOptions.entrySet())
p3 = cb.and(p3, cb.equal(r.get(param.getKey()), param.getValue()));
And the final step to run the query and fetching the results:
At this point I do not know what is the best approach to fill the multiselect criteria with my selections. I would like to insert all items/selections from the List sel to cq.multiselect() with some kind of a loop in a dynamic way...Any idea is welcome!
// This is working but static :(
cq.multiselect(r.get(sel.get(0)), r.get(sel.get(1)), r.get(sel.get(2)));
// i would prefer to have something like
for (int i=0;i<sel.size();i++) {
cq.multiselect().add(r.get(sel.get(i)));
}
Concatenating my WHERE-clause and executing the query:
cq.where(cb.and(p1,p2,p3));
List<Tuple> res = em.createQuery(cq).getResultList();
for (Tuple t : res) {
// do something ...
};
return <something useful>
Following a pseudo SQL query to sum up what I want to achieve:
SELECT {items from List<String> sel}
FROM MyStationDatabase
WHERE (name = selectedStation.get(0) OR ... OR name = selectedStation.get(last))
AND {items from Map<String,Object> selectedOptions}
Well, sometimes it's too trivial to be true -.-
One way to fill the cq.multiselect() with my dynamic list is to just create a list of selections and pass this over to my multiselect-query.
List<Selection<?>> s = new LinkedList<Selection<?>>();
for (String item : sel) {
s.add(r.get(item));
}
cq.multiselect(s);
easy, but maybe someone has the same struggles with this :)
and even if not, see it as an example for a criteriaQuery ;)

Categories