QueryDSL and JPA 2.1 Entity Graph - java

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();
}

Related

Unable to insert null checks when using HAVING clause while building Spring Data JPA Specifications

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]));

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]));
};
}

IllegalArgumentException: Could not locate named parameter

I pass a Map as parameter of the following method:
public List<User> getByParameterOrAll(Map<String, String> paramsMap) {
String email = null;
String emailValue = null;
String phone = null;
String phoneValue = null;
Iterator<Map.Entry<String, String>> entries = paramsMap.entrySet().iterator();
while (entries.hasNext()) {
Map.Entry<String, String> entry = entries.next();
if (entry.getKey().equals("email")){
email = entry.getKey();
emailValue = entry.getValue();
} else if (entry.getKey().equals("phone")){
phone = entry.getKey();
phoneValue = entry.getValue();
}
}
List<User> users = em.createQuery("SELECT u FROM User u WHERE u.email=:emailValue AND u.phone=:phoneValue", User.class).
setParameter(emailValue, email).
setParameter(phoneValue, phone).
getResultList();
return users;
}
And I catch for the line "setParameter(emailValue, email)"
java.lang.IllegalArgumentException: Could not locate named parameter [gmail#gmail.com], expecting one of [phoneValue, emailValue]
I attach the screen of my test
You should correct your query in the following way (see the documentation) :
List<User> users = em.createQuery("SELECT u FROM User u WHERE u.email=:emailValue AND u.phone=:phoneValue", User.class).
setParameter("emailValue", email).
setParameter("phoneValue", phone).
getResultList();

jpa 2 criteria hibernate 5.2 nested joins

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

Spring Hibernate generate dynamic query

I am using hibernate spring where I need to generate query on a condition.
DAO.java
public ReturnData updateUserDetails(Users users, String mailID)
{
if(!users.getImageURL().equals(""))
{
Query query = this.sessionFactory.getCurrentSession().createQuery("UPDATE users SET emailID=:email_ID, name=:name, imageURL=:imageURL WHERE emailID=:emailID")
//setString....
}
else
{
Query query = this.sessionFactory.getCurrentSession().createQuery("UPDATE users SET emailID=:email_ID, name=:name WHERE emailID=:emailID")
//setString....
}
}
In the above code, I check if image also has been uploaded or not. On the basis of this condition, I have to dynamically generate query. I have to rewrite the whole code for query+execution 2 times. Is it the good way, or is there any better way to do this?
You can dynamically append the query conditions to the query string if they are not null. After getting the final list of conditions, you can create Hibernate query.
StringBuilder sqlQuery = new StringBuilder();
Map<String,Object> parameters = new HashMap<String,Object>();
boolean isFirstSearchCriterion = true;
sqlQuery.append("UPDATE users");
if(email_ID!= null && !email_ID.trim().equals("")) {
if(isFirstSearchCriterion) {
sqlQuery.append(" set emailID= :email_ID");
} else {
sqlQuery.append(" and emailID= :email_ID");
}
parameters.put("email_ID",email_ID);
isFirstSearchCriterion = false;
}
if(name!= null && !name.trim().equals("")) {
if(isFirstSearchCriterion) {
sqlQuery.append(" set name= :name");
} else {
sqlQuery.append(" and name= :name");
}
parameters.put("name",name);
isFirstSearchCriterion = false;
}
if(imageURL!= null && !imageURL.trim().equals("")) {
if(isFirstSearchCriterion) {
sqlQuery.append(" set imageURL= :imageURL");
} else {
sqlQuery.append(" and imageURL= :imageURL");
}
parameters.put("imageURL",imageURL);
isFirstSearchCriterion = false;
}
Query query = this.sessionFactory.getCurrentSession().createQuery(sqlQuery);
Set<String> parameterSet = parameters.keySet();
for (Iterator<String> it = parameterSet.iterator(); it.hasNext();) {
String parameter = it.next();
query.setParameter(parameter, parameters.get(parameter));
}
You can simply do without checking empty String, if user has image url it will add in column or else empty url will be pass on.
public ReturnData updateUserDetails(Users users, String mailID)
{
Query query = this.sessionFactory.getCurrentSession().createQuery("UPDATE users SET emailID=:email_ID, name=:name, imageURL=:imageURL WHERE emailID=:emailID")
query.setParameter("imageURL",users.getImageURL(), Hibernate.STRING);
}

Categories