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]));
};
}
Related
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();
}
My code is currently working using the query below and I am converting the query to JPA Specification.
#Query("SELECT DISTINCT h, SUM(m.annualIncome) " +
"FROM Household h LEFT JOIN h.familyMemberList m " +
"GROUP BY h.id " +
"HAVING SUM(m.annualIncome) < 100000 " +
"AND (:householdSize IS NULL OR COUNT(m) = :householdSize) " +
"AND (:householdIncome IS NULL OR SUM(m.annualIncome) = :householdIncome)")
List<Household> findGrantEligibleHouseholds(#Param("householdSize") long householdSize, #Param("householdIncome") long householdIncome);
This is what I have done so far which is working but in an unclean manner.
public static Specification<Household> grantEligibleHouseholdsSpecification(HouseholdCriteria criteria) {
return Specification.where(
(root, query, builder) -> {
List<Predicate> searchCriteria = new ArrayList<>();
final Join<Household, FamilyMember> householdFamilyMemberJoin = root.join(Household_.familyMemberList, JoinType.LEFT);
if(criteria.getHousingType() != null) {
searchCriteria.add(builder.equal(root.get(Household_.housingType), criteria.getHousingType()));
}
query.groupBy(root.get(Household_.id));
if(criteria.getHouseholdIncome() != null && criteria.getHouseholdSize() != null) {
query.having(builder.lt(builder.sum(householdFamilyMemberJoin.get(FamilyMember_.annualIncome)),100000)
,builder.equal(builder.count(householdFamilyMemberJoin),criteria.getHouseholdSize())
,builder.equal(builder.sum(householdFamilyMemberJoin.get(FamilyMember_.annualIncome)),criteria.getHouseholdIncome()));
}
else if(criteria.getHouseholdIncome() != null) {
query.having(builder.lt(builder.sum(householdFamilyMemberJoin.get(FamilyMember_.annualIncome)),100000)
,builder.equal(builder.sum(householdFamilyMemberJoin.get(FamilyMember_.annualIncome)),criteria.getHouseholdIncome() ));
}
else if(criteria.getHouseholdSize() != null) {
query.having(builder.lt(builder.sum(householdFamilyMemberJoin.get(FamilyMember_.annualIncome)),100000)
,builder.equal(builder.count(householdFamilyMemberJoin),criteria.getHouseholdSize()));
}
else {
query.having(builder.lt(builder.sum(householdFamilyMemberJoin.get(FamilyMember_.annualIncome)),100000));
}
query.multiselect();
return builder.and(searchCriteria.toArray(new Predicate[searchCriteria.size()]));
}
);
}
How do I improve this code so in the future it can accept more criteria without going through so many null checks like this? Thanks!
You can use same approach as for searchCriteria - collect multiple predicates into list:
final List<Predicate> havingPredicates = new ArrayList<>();
// default predicates
havingPredicates.add(builder.lt(
builder.sum(householdFamilyMemberJoin.get(FamilyMember_.annualIncome)),
100000));
// custom predicates
if (criteria.getHouseholdIncome() != null) {
havingPredicates.add(builder.equal(
builder.sum(householdFamilyMemberJoin.get(FamilyMember_.annualIncome)),
criteria.getHouseholdIncome()));
}
if (criteria.getHouseholdSize() != null) {
havingPredicates.add(builder.equal(
builder.count(householdFamilyMemberJoin),
criteria.getHouseholdSize()));
}
query.having(havingPredicates.toArray(new Predicate[0]));
I have one user entity in that I have a list of roles, Now I want to fetch user list by specific list of the user using Java specification and predicate. Can anyone help me out? For reference below is the code snnipet.
public List<Users> findByPagingCriteria(SearchModel searchModel, Pageable pageable) {
Page page = userRepository.findAll(new Specification<Users>() {
#Override
public Predicate toPredicate(Root<Users> root, javax.persistence.criteria.CriteriaQuery<?> query,
CriteriaBuilder cb) {
List<Predicate> predicates = new ArrayList<>();
if (searchModel.getName() != null) {
predicates.add(cb.or(cb.like(root.get("firstName"), "%" + searchModel.getName() + "%"),
cb.like(root.get("lastName"), "%" + searchModel.getName() + "%")));
}
if (searchModel.getId() != null) {
predicates
.add(cb.and(cb.equal(root.get("adminDetails").get("adminUniqueId"), searchModel.getId())));
}
if (searchModel.getRole() != null && searchModel.getRole().equalsIgnoreCase("admin")) {
List<Role> roles = Arrays.asList(Role.builder().name("ROLE_ADMIN").build());
predicates.add(cb.and(cb.equal(root.get("roles"), roles)));
}
return cb.and(predicates.toArray(new Predicate[predicates.size()]));
}
}, pageable);
page.getTotalElements(); // get total elements
page.getTotalPages(); // get total pages
return page.getContent(); // get List of Employee
}
Check the roles section, in that I have passed the list of ADMIN role. But while running it throws me an error saying,
java.sql.SQLException: No value specified for parameter 4
I'm trying to add to my crud services the possibility to specify what nested relationship I need so I don't have to read everything from the database.
Take for example I have those entities
Company.java
private List<Department> departments;
private SalaryCode salaryCode;
Department.java
private List<Employee> employees;
private Company company;
private SalaryCode salaryCode;
Employee.java
private Department department;
private SalaryCode salaryCode
And my Criteria query for now is this :
Session session = sessionFactory.openSession();
CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaQuery<T> criteriaQuery = builder.createQuery(clazz);
Root<T> root = criteriaQuery.from(clazz);
//nestedRelationships is a varargs passed as parameters
for(String nestedRelationship : nestedRelationships) {
root.fetch(nestedRelationship, JoinType.LEFT);
}
List<T> result = session.createQuery(criteriaQuery.select(root)).list();
The thing is if I specify "department" as nestedRelationship and querying for Employee entity it works well but when I try to specify "department.salaryCode" it doesn't work saying " Unable to locate Attribute with the the given name ".
Of course I'm fetching "department" first and then "department.salaryCode".
Is it supported? If yes how does it work and if it's not supported what can I do?
Yes,it is supported. You need to use Joins.
Root<Company> root = criteriaQuery.from(Company.class);
Join<Company,Department> joinDepartment = root.join( Company_.departments );
Join<Department,SalaryCode> joinSalaryCode = joinDepartment.join( Department_.salaryCode );
To generate metamodel classes(e.g. Department_ ) have a look at here.
I found a solution by making an algorithm using the Root element
protected void fetch(Root<T> root, String... joins) {
//Sort the joins so they are like this :
//A
//A.F
//B.E
//B.E.D
//B.G
Arrays.sort(joins);
Map<String, Fetch> flattenFetches = new HashMap<>();
for (String join : joins) {
try {
if (join.contains(".")) {
String[] subrelations = join.split("\\.");
Fetch lastRelation = null;
int i;
for (i = subrelations.length - 1; i >= 0; i--) {
String subJoin = String.join(".", Arrays.copyOf(subrelations, i));
if (flattenFetches.containsKey(subJoin)) {
lastRelation = flattenFetches.get(subJoin);
break;
}
}
if (lastRelation == null) {
lastRelation = root.fetch(subrelations[0], JoinType.LEFT);
flattenFetches.put(subrelations[0], lastRelation);
i = 1;
}
for (; i < subrelations.length; i++) {
String relation = subrelations[i];
String path = String.join(".", Arrays.copyOf(subrelations, i + 1));
if (i == subrelations.length - 1) {
Fetch fetch = lastRelation.fetch(relation, JoinType.LEFT);
flattenFetches.put(path, fetch);
} else {
lastRelation = lastRelation.fetch(relation, JoinType.LEFT);
flattenFetches.put(path, lastRelation);
}
}
} else {
Fetch fetch = root.fetch(join, JoinType.LEFT);
flattenFetches.put(join, fetch);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
and to use it I just have to do for example :
employeeController.getAll("punches", "currentSchedule.shifts", "defaultDepartment.currentSchedule.shifts",
"defaultDepartment.company.currentSchedule.shifts", "bankExtras")
I would like to comment the algorithm but I do not have time and it's pretty easy to understand
In my Spring Data/JPA query I need to add filtering with many criteria and the user can choose whatever he want.
Is there a way to get working together QueryDSL and JPA 2.1 Entity Graph ? If so, could you please show an example ?
This is some code from my project using JPA Criteria API. Main idea that user can choose any field as filter and in service layer all filters are passed as List<Map<String, Object>>, where String key in map is a name of field and Object value is filter value. Maybe it will be helpfull:
public List<DocumentsShort> findAllByCriteria(Integer firstResult, Integer maxResult, String sort, String condition, List<Map<String, Object>> conditions) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<EntityClass> criteriaQuery = cb.createQuery(EntityClass.class);
Root<EntityClass> root = criteriaQuery.from(EntityClass.class);
Join<EntityClass, AnotherEntityClass> AnotherEntityClassJoin = root.join("fieldOfEntity", JoinType.LEFT);
Predicate predicate = cb.conjunction();
List<Predicate> predicateList = new ArrayList<>();
for (Map<String, Object> map : conditions) {
Predicate tempPredicate = cb.conjunction();
tempPredicate = cb.and(predicate, cb.equal(root.get("deleted"), 0)); // only entities not marked as deleted
for (Map.Entry<String, Object> entry : map.entrySet()) {
String key = entry.getKey();
Path pathToField = null;
pathToField = root.get(key);
Object value = entry.getValue();
if (value == null) {
tempPredicate = cb.and(tempPredicate, cb.isNull(pathToField));
} else if (value instanceof String) {
tempPredicate = cb.and(tempPredicate, cb.like(pathToField, "%" + value + "%"));
} else if (value instanceof List) {
tempPredicate = cb.and(tempPredicate, pathToField.in(((List) value)));
} else {
tempPredicate = cb.and(tempPredicate, cb.equal(pathToField, value));
}
}
predicateList.add(tempPredicate);
}
criteriaQuery.where(cb.or(predicateList.toArray(new Predicate[predicateList.size()])));
TypedQuery query = entityManager.createQuery(criteriaQuery);
query.setFirstResult(firstResult != null ? firstResult : 0);
query.setMaxResults(maxResult != null ? maxResult : 500);
return query.getResultList();
}