Spring Data (REST) & #Query: optional list as param - java

I'm having the following function in one of my repositories:
#RestResource(path = "filter", rel = "filter")
#Query("SELECT t "
+ "FROM Trip t "
+ "WHERE "
+ "(:from IS NULL OR t.startTs >= :from) "
+ "AND (:categories IS NULL OR t.category IN :categories) ")
Page<Trip> filter(
#Param("from") Instant from,
#Param("categories") List<Category> categories,
Pageable pageable);
The Category is an enum which is stored as:
#Enumerated(EnumType.STRING)
in the Trips table.
When I'm doing my HTTP request with exactly one category I'm getting the correct results. Same behaviour when doing a request without categories key.
htt*://localhost/filter?categories=PRIVATE ==> ok
htt*://localhost/filter ==> ok
When using more than one category:
htt*://localhost/filter?categories=PRIVATE,BUSINESS
I'm getting the following exception:
org.hibernate.hql.internal.ast.QuerySyntaxException: unexpected AST
node: {vector} [select count(t) FROM foo.bar.services.trips.model.Trip
t WHERE (:from IS NULL OR t.startTs >= :from) AND (:categories_0_,
:categories_1_ IS NULL OR t.category IN (:categories_0_,
:categories_1_)) ]
Anybody have an idea what I'm doing wrong here?

Try one of the following:
1) Try to enclose the statements involving the list in parentheses:
#Query("SELECT t "
+ "FROM Trip t "
+ "WHERE "
+ "(:from IS NULL OR t.startTs >= :from) "
+ "AND ((:categories IS NULL) OR (t.category IN :categories)) ")
2) Enclose the :categories in parentheses here
t.category IN (:categories)

Related

HQL QuerySyntaxException: unexpected AST node

Searched and found a lot of questions about this but nothing for my particular case. I am getting an error on my HQL query, here is the code:
final Query query = session.createQuery(
" SELECT DISTINCT e " +
" FROM Employee e" +
" INNER JOIN Requisition r on r.supervisor = e.id " +
" WHERE r.status = 'Open' " +
" AND r.isEvergreen = false " +
" AND r.isConfidential = false " +
" AND r.employmentType != 'Intern (Fixed Term)' " +
" AND (" +
" CASE WHEN :searchString IS NOT NULL THEN (CONCAT(e.firstName, ' ', e.lastName) LIKE CONCAT('%', TRIM(:searchString), '%')) END)" +
" ORDER BY e.firstName, e.lastName")
.setParameter("searchString", searchString);
And here's the error log:
org.hibernate.hql.internal.ast.QuerySyntaxException: unexpected AST node: CASE near line 1, column 329 [ SELECT DISTINCT e FROM <Insert the rest of the query>
The query works if I remove the CASE WHEN statement in the final AND and the setParameter. So that means something is wrong with the final AND (...) and/or the newly introduced parameter.
I am new to Hibernate and am struggling with this since the error message isn't super helpful. Any ideas?
The case you have does not complete the condition.
You can simplify the case as follows :
(coalesce(:searchString,1,0) =1 OR e.firstName||' '|| e.lastName LIKE '%'||:searchString||'%')
The first coalesce will check if searchstring is null , if it is will return 1 and 0 if it is not.
If it returns 1 , the expression after OR will not get evaluated.
If first expression return 0 , the expression after OR will be evaluated
This will fulfill your usecase to apply searchString filter only when it is not null.

JPA repository findBySOMETHING, could not extract result

I'm using JPA repository for some toy project, I want to get List of object SOMETHING, but result is could not extract ResultSet; SQL[n/a].
This is my object (nothing is important, just focus on lob plz)
{
...
private Long id
#Lob
#Column(columnDefinition = "text")
private String content;
...
}
Then, I use repository to findByContentLike("CONTENT"), then result is failed, the reason is that cannot extract result.
why this happened? even i set on application.properties like spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
there anyone know about this error? please bring me to the light.
If you need auto parameter bound wrapped in %(wildcard) use
findByContentContaining("Content")
Or you need to manually add %(wildcard) in the string
findByContentLike("%Content%")
JPA-methodqueries require a special syntax. Most commonly they are build up as: findBy + a field of your entity. In your case, the like is redundant.
See this reference to the docs for examples.
As complexity increases, you might want to write your own queries, using JPQL. A small example:
#Query("SELECT p FROM Product p "
+ "LEFT JOIN p.categories category "
+ "WHERE UPPER(p.name) LIKE UPPER(CONCAT('%', COALESCE(:searchRequest, ''), '%')) "
+ "AND UPPER(p.description) LIKE UPPER(CONCAT('%', COALESCE(:description, ''), '%')) "
+ "AND p.price BETWEEN :priceLow AND :priceHigh "
+ "AND p.averageRating >= :averageRating "
+ "AND p.archived = :archived "
+ "AND ((category.name IN :selectedCategories) "
+ "OR (:amountOfSelectedCategories = 0 AND category IN (SELECT c FROM Category c))) "
+ "GROUP BY p "
+ "HAVING SIZE(p.categories) >= :amountOfSelectedCategories"
)
Page<Product> findAllBySearchModel(
Pageable pageable,
#Param("searchRequest") String searchRequest,
#Param("description") String description,
#Param("priceLow") BigDecimal priceLow,
#Param("priceHigh") BigDecimal priceHigh,
#Param("averageRating") double averageRating,
#Param("archived") boolean archived,
#Param("selectedCategories") List<String> selectedCategories,
#Param("amountOfSelectedCategories") int amountOfSelectedCategories
);
Don't forget to mark your entity class with #Entity and to have your repository extend JpaRepository<MyEntityClass, Long> where the Long corresponds with your entity's #Id field.

How to get data based on 'AND' operator instead of 'LIKE' by using JpaSpecificationExecutorWithProjection's findAll method?

I am new to Hibernate.My senior used 'findAll' method of JpaSpecificationExecutorWithProjection (interface) which is returning result based on a 'LIKE' operator but I required result based on 'AND' operator. Please guide me how I can solve this?.
Below is the part of code which displays it is hitting query based on 'LIKE' operator
where
(
upper(course0_.course_subject) like ?
)
and (
upper(course0_.course_sub_category) like ?
)
and course0_.course_status=?
and (
upper(course0_.course_exam_segment) like ?
)
and (
upper(course0_.course_category) like ?
)
It is using AND operator. The LIKE is for evaluating your individual params.
If you don't like the JPA-methodqueries, you can always write your own in your repository. Below is a quick example incorporating a left join and returning a Page-object. Please take a look here for some basic JPQL
#Query("SELECT p FROM Product p "
+ "LEFT JOIN p.categories category "
+ "WHERE UPPER(p.name) LIKE UPPER(CONCAT('%', COALESCE(:searchRequest, ''), '%')) "
+ "AND UPPER(p.description) LIKE UPPER(CONCAT('%', COALESCE(:description, ''), '%')) "
+ "AND p.price BETWEEN :priceLow AND :priceHigh "
+ "AND p.averageRating >= :averageRating "
+ "AND p.archived = :archived "
+ "AND ((category.name IN :selectedCategories) "
+ "OR (:amountOfSelectedCategories = 0 AND category IN (SELECT c FROM Category c))) "
+ "GROUP BY p "
+ "HAVING SIZE(p.categories) >= :amountOfSelectedCategories"
)
Page<Product> findAllBySearchModel(
Pageable pageable,
#Param("searchRequest") String searchRequest,
#Param("description") String description,
#Param("priceLow") BigDecimal priceLow,
#Param("priceHigh") BigDecimal priceHigh,
#Param("averageRating") double averageRating,
#Param("archived") boolean archived,
#Param("selectedCategories") List<String> selectedCategories,
#Param("amountOfSelectedCategories") int amountOfSelectedCategories
);

Implement JPA Projection with count

I want to implement JPA Projection with count. I tried this:
#Query(value = "SELECT new org.service.PaymentTransactionsDeclineReasonsDTO( id, count(id) as count, status, error_class, error_message) " +
" FROM payment_transactions " +
" WHERE terminal_id = :id AND (created_at > :created_at) " +
" AND (status != 'approved') " +
" GROUP BY error_message " +
" ORDER BY count DESC", nativeQuery = true)
List<PaymentTransactionsDeclineReasonsDTO> transaction_decline_reasons(#Param("id") Integer transaction_unique_id, #Param("created_at") LocalDateTime created_at);
But I get error: Caused by: java.sql.SQLException: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '.plugin.service.PaymentTransactionsDeclineReasonsDTO( id, count(id) as c' at line 1
How I can implement proper count when I have class based Projection?
Try Interface-based Projection instead of DTO:
public interface TransactionDeclineReason {
Integer getId();
Long getCount();
Status getStatus();
ErrorClass getErrorClass(); // I suppose it's enum...
String getErrorMessage();
}
#Query(value = "select " +
"id as id, " +
"count(id) as count, " +
"status as status, " +
"error_class as errorClass, " +
"error_message as errorMessage " +
"from " +
"payment_transactions " +
"where " +
"terminal_id = ?1 " +
"and created_at > ?2 " +
"and status != 'approved' " +
"group " +
"by error_message " +
"order by " +
"2 desc", nativeQuery = true)
List<TransactionDeclineReason> getTransactionDeclineReasons(Integer transactionId, LocalDateTime createdAt);
Pay attention on aliases (i.e. id as id) - they are mandatory.
You are mixing JPQL and SQL syntax.
The constructor expression (new ...) is JPQL, but in the annotation you mark it as a nativeQuery i.e. as SQL so you have to make up your mind.
If it is to be SQL I don't think you can use aliases in the ORDER BY clause, so you might have to either repeat the expression or wrap it in a subselect as described here: Using an Alias in a WHERE clause.
If it is to be JPQL it doesn't support aliases in a constructor expression, so I guess you have to repeat the expression in the order by clause.

JPA 'where in' to be considered only if List passed as parameter has elements

I have one query that should filter based on various parameters; one of those parameters is a list. If there are entries in the list, there should be a filtering based on the entries; but if the list is empty/null, there shouldn't be any filtering on that field.
What I've thought is something like this:
#Query("select a from Alert a where a.date >= :startDate " +
"and (((:countryIds) is null) or a.countryId in (:countryIds)) " +
"and (((:typeIds) is null) or a.siteTypeId in (:typeIds)) ")
List<Alert> findBy(#Param("startDate") Date startDate,
#Param("countryIds") Set<Long> countryIds,
#Param("typeIds") Set<Long> typeIds);
Sending null List it throws NPE; sending an empty list it generates the following SQL, which is invalid
where alert0_.date >= '2018-01-01' and
((1, 123) is null or alert0_.countryId in (1, 123))
I've also tried in JPQL to have and (((:countryIds) is empty) or a.countryId in (:countryIds)) but it also doesn't work when trying to compile the JPQL (at application startup): Caused by: org.hibernate.hql.internal.ast.QuerySyntaxException: ??? is not mapped
at org.hibernate.hql.internal.ast.util.SessionFactoryHelper.requireClassPersister(SessionFactoryHelper.java:171)
Or using SpEL:
"and (:#{countryIds.size() > 0} or (a.countryId in (:countryIds))) "
but again, it doesn't compile the JPQL.
The only solution I've thought is to dynamically generate the JPQL which is ugly or to populate all existing values for countryIds and siteTypeIds which is inefficient.
JPA implementation is Hibernate and database is MySQL.
After lots of trial and error I found an acceptable working solution with SpEL; thought some might find it useful:
#Query("select a from Alert a where a.date >= :startDate "
"and (:#{#countryIds == null} = true or (a.countryId in (:countryIds))) " +
"and (:#{#siteTypeIds == null} = true or (a.siteTypeId in (:siteTypeIds))) ")
List<Alert> findBy(#Param("startDate") Date startDate,
#Param("countryIds") Set<Long> countryIds,
#Param("siteTypeIds") Set<Long> siteTypeIds);
The Sets sent as parameters have to be null instead of empty sets.
It yields an acceptable SQL:
select alert0_.alertId as alertId1_0_, [...]
from alert alert0_
where alert0_.date >= '2018-01-01' and
(0 = 1 or alert0_.countryId in (1, 123)) and
(1 = 1 or alert0_.siteTypeId in (null));
I had the same problem so im writing extended solution with using also embedded parameter
#Query("from PartPrice where "
+ "customer in :#{#customers} and "
+ "( (:#{#suppliers == null || #suppliers.size() == 0} = true and supplier is null) or (:#{#suppliers != null && #suppliers.size() > 0} = true and supplier in :#{#supplier}) ) and "
+ " productIdentifier.manufacturerId = :#{#productIdentifier.manufacturerId} and productIdentifier.productNumber = :#{#productIdentifier.productNumber} and "
+ " ( (:#{#isAbsPrice} = true and abs_price is not null) or (:#{#isAbsPrice} = false and abs_price is null) ) "
+ " and (validUntil is null or validUntil >= :#{#fromDate}) and (:#{#untilDate == null} = true or validFrom <= :#{#untilDate}) ")
where suppliers is nullable, empty or contains values and productIdentifier is embedded id containing productNumber and manufacturerId passing as
#Param("productIdentifier") ProductIdentifier productIdentifier
Also interval is valid from fromDate to null (forever) or untilDate.
As for me the best solution for such cases is Criteria API, if you not familiar with it you can find some information here:
https://www.objectdb.com/java/jpa/query/criteria

Categories

Resources