Spring JPA #Query throws Optional parameter exception with Specification and Param - spring

I'm writing a larger query in JPA and need the pagination, specification and an additional parameter to work in it. I wanna function look like this.
#Query(value = QRY_DATA, countQuery = QRY_DATA_COUNT)
Page<NotificationUserSettingsPageResponse> findAllFrontend(#Nullable Specification<NotificationUserSettings>
spec, Pageable pageable, #Param("user_UUID") String userUUID);
when i try perform query like this i get error
but parameter 'Optional[spec]' not found in annotated query
when i delete last param from query and function it work fine but when i add param JPA try to use Specification like param.
I mention that i need this param to use in join so i can't move it to specification. Query below.
SELECT new pl.notification.dto.response.NotificationUserSettingsPageResponse(s.id,
s.userUUID, n.id,
n.system, n.name, n.category,
b.id, b.businessRule,
CASE WHEN s.id > 0
THEN s.isEmail ELSE 'T' END AS is_email,
CASE WHEN s.id > 0
THEN s.isSMS ELSE 'T' END AS is_sms,
CASE WHEN s.id > 0
THEN s.isPortal ELSE 'T' END AS is_portal)
FROM BusinessRuleConfig b
RIGHT JOIN NotificationConfig n on n.id = b.notificationConfig.id
LEFT JOIN NotificationUserSettings s on s.id = n.id AND s.id = b.id AND s.userUUID =:user_UUID

Seems, you are extend just the JpaRepository for your repository.
You may extend your repo:
#Repository
public interface NotificationUserSettingsPageResponseRepository extends JpaRepository<NotificationUserSettingsPageResponse, Long>, JpaSpecificationExecutor<NotificationUserSettingsPageResponse> {
#Query(value = QRY_DATA, countQuery = QRY_DATA_COUNT)
Page<NotificationUserSettingsPageResponse> findAllFrontend(#Param(value = "user_UUID") String userUUID, Pageable pageable);
}
P.S. And use Pageable argument the last.

Related

How to add optional parameter in JPQL?

How to use/add optional parameter in JPQL?
#Query(value = "SELECT stud FROM Student stud where stud.name = :studentName AND stud.age IN :studentAgeList")
List<Student> getStudents(
#Param("studentName ") String studentName,
#Param("studentAgeList") List<Integer> studentAgeList
)
How to make studentAgeList parameter in above query ?
I tried below :
#Query(value = "SELECT stud FROM Student stud where stud.name = :studentName AND (:studentAgeList IS NULL OR stud.age IN :studentAgeList))
List<Student> getStudents(
#Param("studentName ") String studentName,
#Param("studentAgeList") List<Integer> studentAgeList
)
But getting error : unexpected AST node:
Tried above but getting error
JPQL does not support optional parameters, you can use overload methods with different queries or criteria API or JPA specifications.
may be get answer here: Set optional parameters in JPQL Query

Spring Data - Custom DTO Query with filtering

I have a complexe application and I need to retrieve and filter 1000~5000 object for an xls export. Each object having multiple eager relationship (I need them for the export).
If I retrieve all the objects and their relationship as it is, I got some stackoverflow error.
Generaly when I need to make a big export, in order to make it efficient I use a DTO object with an #Query like this :
public interface myRepository extends JpaRepository<Car, Long> {
#Query("SELECT new com.blabla.myCustomObject(p.name, p.surname, c.model, c.number ...) "
+ "FROM Car c "
+ "LEFT JOIN c.person p "
+ "WHERE ... ")
List<myCustomObject> getExportCustomObject();
}
The problem is that the #Query is static and I want to add dynamic filter to my Query (Specifications, Criteria or some other system...)
How to do it ?
Specification cannot be used because this is only the where clause.
But you can use Criteria API. Here's an example. The BasicTeacherInfo is the DTO:
CriteriaQuery<BasicTeacherInfo> query = cb.createQuery(BasicTeacherInfo.class);
Root<Teacher> teacher = query.from(Teacher.class);
query.multiselect(teacher.get("firstName"),teacher.get("lastName"));
List<BasicTeacherInfo> results = em.createQuery(query).getResultList();
You can use #Param annotation to pass dynamic values to HQL, something like:
#Query("SELECT new com.blabla.myCustomObject(p.name, p.surname, c.model, c.number ...) "
+ "FROM Car c "
+ "LEFT JOIN c.person p "
+ "WHERE c.status = :status AND p.name = :name")
List<myCustomObject> getExportCustomObject(
#Param("status") Integer status,
#Param("name") String name
);
Below is one of the possible way where you can try to add offset and limit into your query you can make it dynamic with the help off placeholders.
Below is an sample pseudo code for reference:
Dao Layer:
#Query(value="SELECT e FROM tablename e WHERE condition_here ORDER BY e.id offset :offset limit:limit ")
public returnType yourMethod(String name, int offset, int limit);
Service Layer:
long count = number of records in db.
int a = // number of records to be fetched on each iterations
int num_iterations = count % a ;
int additionalrecords = count / a;
int start= 0;
while(num_iterations>0)
{
dao.yourMethod(start,a);
start = start+a;
count--;
// write your data to excel here
}
dao.yourMethod(start,additionalrecords);
Hope it is helpful.

Join 2 Tables using Spring Boot JPA Criteria Specification and Predicate

I'm trying to create a criteria to retrieve Rows from 2 tables (UserDetail, Vehicle). A UserDetail has reference to Vehicle. My objective is to retrieve a list of specified field from both Tables, given an UserDetail id. In #Query was easy to do but the client said that this must be coded with criteria with specification and predicate to Join Tables.
My #Query was:
#Repository
public interface UserDao extends CrudRepository<UserDetail, Integer>{
#Query("SELECT ud.userId, ud.userName ,vh.vehicleId, vh.vehicleName "
+ "FROM UserDetail ud LEFT JOIN ud.vehicle vh where ud.vehicleId = vh.vehicleId")
public List<UserVehicleDTO> findAllUserVehicle();
}
Can anyone help me? I Fetch Data Using Specification with predicate as given Query is needed
Criteria query(I did not tested/debugged it, may need modify):
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> query = cb.createTupleQuery();
Root<UserDetail> root = query.from(UserDetail.class);
Join<UserDetail, Vehicle> join = root.join("vehicle", JoinType.LEFT);
query.multiselect(
root.get("userId"),
root.get("userName"),
join.get("vehicleId"),
join.get("vehicleName")
))
List<Tuple> x = entityManager.createQuery(query).getResultList();
BTW. JPQL query is wrong. Don't need where condition, it will be created by JPA automatically.
SELECT ud.userId, ud.userName ,vh.vehicleId, vh.vehicleName
FROM UserDetail ud
LEFT JOIN ud.vehicle

How to calculate average on a column with JPA?

I would like to calculate average on a column.
I tried the following:
#Query("SELECT AVG(e.rating) FROM user_rating e WHERE e.route_uid = ?1")
fun averageOfRateings(routeId: UUID): Long
The query works in Sql, however I get the following error when I run the code in Spring Boot.
Caused by: java.lang.IllegalArgumentException: org.hibernate.hql.internal.ast.QuerySyntaxException:
user_rating is not mapped [SELECT AVG(e.rating) FROM user_rating e WHERE e.route_uid = ?1]
What would be the right syntax? What is the problem with the mapping of my table?
you must use the entity name, not the table.The same applies for the column name, you must use the field name instead.
I assume your entity is UserRating, so the correct query will be:
#Query("SELECT AVG(e.rating) FROM UserRating e WHERE e.routeUid = ?1")
Or second option specify that is native query:
#Query(value = "SELECT AVG(e.rating) FROM user_rating e WHERE e.route_uid = ?1" , nativeQuery = true)

Filter with Spring-Data and QueryDSL on nullable reference attribute

I have following issue. I am using Jquery Datatable serverside and I am now implementing the search box. But I have an issue there in special case, when a dataset has an attribute, what is "null". So the dataset will not be found although it should found cos it matches on one attibute.
The situtation in beginning is like follows. You see there is a dataset with apprentice Fabio Bartels, who has not Fachrichtung. And a dataset with Viktoria.
Now when I search for Viktoria, the filter works as expected:
When I search for Fabio, then Dataset is not found:
=====
The problem I have is, that I don't know how to handle the filter, that a attribute will only be validated against the search string when the attribute is not null.
=====
Serverside Java Classes see like follows:
QueryClass:
class ContractSearchQuery {
private static QContract contract = QContract.contract;
static BooleanExpression getPredicate(final ContractSearch filter) {
BooleanExpression predicate;
if (filter == null || filter.isEmpty()) {
// SHOW ALL PREDICATE ...
} else {
final String search = filter.getSearch();
final List<BooleanExpression> expressions = new ArrayList<BooleanExpression>();
// EXPRESSIONS CURRENTLY ONLY ON AUSZUBILDENDER AND FACHRICHTUNG
// FOR SHOWCASE
expressions.add(containsApprenticeName(search)); // AUSZUBILDENDER
expressions.add(containsSpecialisation(search)); // FACHRICHTUNG
BooleanExpression expression = expressions.get(INTEGER_ZERO);
for (int i = 1; i < expressions.size(); i++) {
expression = expression.or(expressions.get(i));
}
predicate = expression;
}
return predicate;
}
private static BooleanExpression containsApprenticeName(final String search) {
final BooleanExpression expLastName = contract.apprentice.lastName.containsIgnoreCase(search);
final BooleanExpression expFirstName = contract.apprentice.firstName.containsIgnoreCase(search);
return expLastName.or(expFirstName);
}
private static BooleanExpression containsSpecialisation(final String search) {
return contract.companyOccupationCombination.occupationCombination.specialisation.name.containsIgnoreCase(search);
}
}
Spring-Data-Repository Call:
final PageRequest pageRequest = new PageRequest(firstResult / maxResults, maxResults, orderSort);
final Page<Contract> page = contractRepository.findAll(predicate, pageRequest);
return page.getContent();
=======
Database:
By the way I recognized when I do direct request against my db with joining Specialisation Table, then I only get Fabio as record, when not joining Specialisation, I get all three persons. Maybe somethings to do with my issue:
select a.first_name, a.last_name from contract c
join company_occupation_combination coc on c.company_occupation_combination = coc.id
join occupation_combination oc on coc.occupation_combination = oc.id
join apprentice a on c.apprentice = a.id
Result:
"Fabio";"Bartels"
"Viktoria";"Kruczek"
"Lina";"Ehleiter"
With Join:
select a.first_name, a.last_name from contract c
join company_occupation_combination coc on c.company_occupation_combination = coc.id
join occupation_combination oc on coc.occupation_combination = oc.id
join specialisation s on oc.specialisation = s.id
join apprentice a on c.apprentice = a.id
Result: "Viktoria";"Kruczek"
====
EDIT:
Okay, on db site I found out (with Hibernate and JPA I start forgetting SQL-Basices ;-)), that I need a left join for the nullable relation, so my query should result to an sql like:
select a.first_name, a.last_name from contract c
join company_occupation_combination coc on c.company_occupation_combination = coc.id
join occupation_combination oc on coc.occupation_combination = oc.id
left join specialisation s on oc.specialisation = s.id
join apprentice a on c.apprentice = a.id
====
So my question is, how can I manage left Join when I have a Query-Class using QueryDSL and Spring-Data-Repository like mentioned above?
If you really need left join, you can't achieve that via predicate (instead it is possible via sub-query)
To be able to do left-join, you will need JPAQuery.
Assuming you have already configured repositories, and able to use EntitiManager, implement ContractRepositoryCustom , so that in your implementation you can have
#PersistenceContext(unitName = "unitname")
protected EntityManager entityManager;
public List<Contract> findAllContracts() {
return new JPAQuery(entityManager, HQLTemplates.DEFAULT)
.from(QContract.contract)
.join(QContract.contract.companyOccupationCombination, QCompanyOccupationCombination.companyOccupationCombination)
.join(QCompanyOccupationCombination.companyOccupationCombination.occupationCombination, QOccupationCombination.occupationCombination)
.leftJoin(QOccupationCombination.occupationCombination.specialization, QSpecialization.specialization)
.join(QSpecialization.specialization.apprentice, QApprentice.apprentice)
.list(QContract.contract);
}
And for pagination you always apply limit(maxResults) and offset(firstResult)
I really like working with Spring-Data and Query-DSL, cos it makes my code really tidy. But I am really suprised, that for the case of nullable references there seems no solution. Sure you can use another solution like #vtorosyan mentioned and thank you again for that solution, but when you project is builded up with combination of QueryDSL and Spring-Data, you really don't want to bring a second style in your application.
But I needed a solution, so I did now the follows.
The point of the issue was, that when I used data from a nullable entity, a join has been executed what hided the datasets, who had a null reference on it, see examples above. What I now did and I hope I will not get another issue then with that solution on later time of that project. I did the null references to not null and defined something like null-record.
Example I added a record for specialisation like
ID NAME
0 Keine
Instead of null I now use that record what has until now following effects:
First my table shows now "Keine" (engl. "None") for all attributes what are not set. It looks more consistent when having a textoutput then empty string.
Now I can explicitly search for "Keine", when I am interested for data records what have no specialisation set.
And my searchbox works as expected for records, which have no speciafication set. (THAT WAS MY ISSUE FROM BEGINNING WHAT I WANTED TO SOLVE):
Additional to that searchbox I use a modal dialog for filtering. Now I can explicitly filter "Keine" for "nullable" records:
If you think there is another good solution for that issue without rebuild code using Spring-Data and QueryDSL konsequently, don't hesitate to post ;-)

Resources