Bind values to search String into table column - spring

I'm trying to implement a search functionality into table column using this code:
#Query(
value = "SELECT * " +
"FROM ACTIVE_PAIRS ap " +
"WHERE ap.pair iLIKE CONCAT('%', ?1, '%') " +
"LIMIT ?2 " +
"OFFSET ?3"
)
Flux<ActivePairsFullDTO> findAllBySearchParam(String params, long limit, long offset);
But I get error: Binding parameters is not supported for the statement
Do you know how I can fix this issue?

I'm a little surprised that you are getting this error, because I would have thought the first error you should get is that your statement is not valid JPQL, nor HQL since LIMIT and OFFSET aren't part of JPQL.
In order to use SQL you need to specify nativeQuery=true in the annotation.
Since you also return a Flux I'm not sure you use Spring Data JPA as tagged, but Spring Data R2DBC?
Either way, the exception you are seeing stems from the fact that some databases don't accept bind variables for LIMIT and OFFSET.
For Spring Data JPA you can work around this by simply removing those clauses and add a PageRequest argument to the method.
This approach is not supported for Spring Data R2DBC. Instead you would use SpEL expressions to include the LIMIT and OFFSET values.

Related

How to Query Spring Data Neo4j with List Argument

I am trying to execute the query "find all merchants whose ids are in this list" (where the id is #GeneratedValue UUID string) in my spring data neo4j application.
Using query methods, that would (AFAIK) translate to either of:
List<Merchant> findByIdIn(List<String> ids);
List<Merchant> findByIdIsIn(List<String> ids);
In my swagger UI, I can see the endpoints, but when I pass in a valid id, no results are returned:
However, if I execute the same method programatically, the correct results are returned:
Similarly, the GET /merchants/90c55d4b-b3dc-4ae3-ab43-4d6ff523a20b endpoint returns the expected element.
This leads me to believe the internal translation of the HTTP request to the query isn't correct (most likely due to the list arg). The exact same thing happens (no results are returned) if I use a custom #Query, despite using the accepted solution from this s/o question:
#Query("MATCH (m: Merchant)" +
"WHERE m.id in $merchantId " +
"RETURN m")
List<Merchant> customQuery(List<String> merchantIds);
Am I missing something here? How do I query using a list as an argument? Thanks!

Spring data jpa projection could not extract ResultSet

I'm trying to map my entity to projection using the below query but i'm getting error as
Exception : could not extract ResultSet SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not extract ResultSet
here is the query
#Query("select rf.rfqID as rfqID,rf.creationDate as creationDate," +
"rf.deadLineDate as deadLineDate,rf.details as details," +
"rf.message as message, rf.rfqDoc as rfqDoc," +
"CASE WHEN (rf.creationDate > CURRENT_DATE) THEN 'open' ELSE 'closed' END as status," +
"rf.rfqMembers as rfqMembers " +
"from RFQ rf where rf.createdBy = ?1")
Page<RfqDto> loadAllRfq(String creator, Pageable pageable);
In my Dto I have an extra status column which I don't want to persist in db and would like to get the status via query
here is my projection interface
public interface RfqDto {
String rfqID();
Date creationDate();
Date deadLineDate();
String details();
String message();
String rfqDoc();
String status();
List<RfqMember> rfqMembers();
}
The root cause of your problem is here:
In my Dto I have an extra status column which I don't want to persist in db and would like to get the status via query
As it's explained in the documentation:
The important bit here is that the properties defined here exactly match properties in the aggregate root.
...
The query execution engine creates proxy instances of that interface at runtime for each element returned and forwards calls to the exposed methods to the target object.
So, you can not use spring data jpa projection for your case. You can not use hibernate/jpa projection as well, because it dose not support collections in row results.
You can try to use Blaze-Persistence Entity Views. See for example this answer.

Prevent JPQL query sql injection

I was advised that below query is not safe as parameter :searchFor coming from input field in front end can be used for SQL injection.
Plase advise what is the best solution to prevent SQL injection in below code?
#Query("SELECT u FROM User u WHERE lower(u.username) LIKE %:searchFor% " +
" OR lower(concat(u.firstname, ' ', u.lastname)) LIKE %:searchFor% " +
" OR lower(u.email) LIKE %:searchFor%")
Page<User> findAllAndSearch(#Param(value = "searchFor") String searchFor, Pageable pageable);
I am not using "+" to join strings, but provide parameter instead (:searchFor). Not sure if this still unsafe.
I was advised that below query is not safe as parameter :searchFor
You should challenge this advise.
SQL injections occur when the parameter value passed by the client may transmit additional query logic (generally undesirable) and that that will be allowed in the executed query such as.
For example instead of a simple foo textual value for searchFor, the parameter value could contain also additional logic in the query, for example : foo OR ''=''. That is SQL injection.
In your case, SQL injection is not possible because you don't set the parameter manually but rather you rely on a safe way to bind thesearchFor parameter : Spring.
Indeed, Spring binds the parameter values in a safe way as JPA implementations do, that is setting the parameter value from a JPA Query instance that is protected against SQL injections for declared parameters.
For example take this query (I removed the % part to simplify) :
"SELECT u FROM User u WHERE lower(u.username) LIKE :searchFor"
And try to set the param of searchFor with the String "foo OR ''==''" to try to inject a SQL condition that is always true.
If you turn on the log of your JPA implementation to output the paramaters binding (for Hibernate : logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE) you could see something like :
TRACE 11012 --- [nio-8080-exec-8] o.h.type.descriptor.sql.BasicBinder
: binding parameter [1] as [VARCHAR] - [foo OR ''=='']
The binding is only performed for the parameter value and not as a way to add new query logic. The final query part protected is so protected as :
SELECT u FROM User u WHERE lower(u.username) LIKE "foo OR ''==''"
I believe the underlying ORM framework would sanitise input if passed as parameter this way.

Spring JPA with native query and data projection mapping the wrong columns into the projected interface

I've got a bit of a bizarre problem that I can't figure out why it's happening. I'm sure I did something wrong, because this is my first time using a data projection and I've never had such problems using DTOs.
Pretty much I have a SELECT statemen that is returning certain columns of various data types. And I have an interface that I'm passing to the JPA Repository so it can do the interface mapping. But instead of mapping the results based on the column name (eg. 'accountnum' -> getAccountnumber()), it's mapping the columns in alphabetical order. So if 'date_of_order' is the first in the SELECT statement, its value will be returned by getAccountnumber().
I have a projected interface that looks something like this:
public interface FlatSearchResult {
String getAccountnumber();
UUID getTrackingId;
Date getDateOfOrder;
}
My model has three tables something like this:
ACCOUNT
- account_id : uuid (pkey)
- accountnumber : string
ORDERS
- order_id : uuid (pkey)
- date_of_order : timestamp
- account_id : uuid (fkey)
TRACKING
- tracking_id : uuid (pkey)
- order_id : uuid (fkey)
There's other columns in each of those tables, but they're not relevant.
I have a repository defined with a simple query:
public interface OrderTrackingRepository extends JpaRepository<Account, UUID> {
#Query( nativeQuery = true,
value = "SELECT o.date_of_order, a.accountnumber, t.tracking_id " +
"FROM account as a " +
"INNER JOIN orders as o USING (account_id) " +
"INNER JOIN tracking as t USING (tracking_id) " +
"WHERE a.accountnumber = :acctnum")
<T> Collection<T> findOrderInfoForAccount(#Param("acctnum") acctNumber, Class<T> type);
}
When I call this method, the correct rows are returned by the query. But instead of mapping using the column name (eg. date_of_order to getDateOfOrder()), it is mapping based on the order of the columns in the SELECT statement to the alphabetically-ordered methods in the interface.
So:
SELECT date_of_order, accountnumber, tracking_id
Results in:
getAccountNumber() -> date_of_order
getDateOfOrder() -> accountnumber
getTrackingId() -> tracking_id
It will consistently return in this fashion, so it's not a transient issue.
As a temporary workaround, I've reordered the columns in my SELECT statement. But I would rather not have to do this since it's like iterating through a result set and relying on column position, which just makes me twitchy....
How can I get Spring JPA to map from the result set to my interface? Do I need to annotate my projection interface's methods with something to tell Spring what column name it's referring to?
My database is Postgres. I'm using Spring 5.0.2.RELEASE and Spring-Boot 2.0.0.M7. I can adjust either of those to newer versions if needed, but nothing older. I'm using C3P0 0.9.5.2 for my connection pooling, and postgres-9.2-1002.jdbc4. All my other dependencies (hibernate, etc) are what is pulled in by this version of Spring-Boot.
Not sure if this is the correct solution because it only fits 80% of the description. But it is too long for a comment. So here we go.
I think you misunderstood #osamayaccoub or the documentation. Your property name is fine. But the columns in your select should match the java convention.
So the first attempt to fix that would be
value = "SELECT o.date_of_order as dateOfOrder, a.accountnumber as accountNumber, t.tracking_id as trackingId "
Note: This might actually work, but might break later, so read on, even if it does work
But Postgres converts everything that isn't double quoted into lower case (Oracle and MySql do similar stuff though details vary, don't know about other DBs yet). So you really should use:
value = "SELECT o.date_of_order as \"dateOfOrder\", a.accountnumber as \"accountNumber\", t.tracking_id as \"trackingId\" "
This probably doesn't work, because the Hibernate version you are using has a bug in that it converted everything to lower case.
So you should upgrade to the latest Hibernate version 5.3.13 which has the issue fixed.
This bug fix interestingly might break the version without the double quotes.
But it should work again with this PR for this Spring Data JPA issue.
The part I don't understand is, why stuff gets assigned using the column order.
I had the same problem and i solved by odering the query columns alphabetically.
In you case:
public interface OrderTrackingRepository extends JpaRepository<Account, UUID> {
#Query( nativeQuery = true,
value = "SELECT a.accountnumber, o.date_of_order, t.tracking_id " +
"FROM account as a " +
"INNER JOIN orders as o USING (account_id) " +
"INNER JOIN tracking as t USING (tracking_id) " +
"WHERE a.accountnumber = :acctnum")
<T> Collection<T> findOrderInfoForAccount(#Param("acctnum") acctNumber, Class<T> type);
}
So you will get:
getAccountNumber() -> accountnumber
getDateOfOrder() -> date_of_order
getTrackingId() -> tracking_id
Hibernate sorts the query in alphabetic order so you have to change the select to: "SELECT a.accountnumber, o.date_of_order, t.tracking_id ..." and the interface's getters should follow the same alphabetic order.

Using Postgres JSONB query with Spring Data and bind parameter fails with InvalidDataAccessApiUsageException

I am currently looking for a solution for the exception
org.springframework.dao.InvalidDataAccessApiUsageException: Parameter with that position [1] did not exist;
My current #Query annotation is:
#Query(
nativeQuery = true,
value = "SELECT * FROM thgcop_order_placement WHERE \"order_info\" #> '{\"parentOrderNumber\":\" :param \"}'")
I guess the position [1] did not exist comes from it being in double quotes plus double quote plus single quote.
How can I make this work?
The query is using Postgres JSONB datatype. The column definition is ORDER_INFO JSONB
The following native query works just fine in the Postgres client:
SELECT * FROM thgcop_order_placement
WHERE "order_info" #> '{"parentOrderNumber":"ORD123"}'
None of the above worked for me except the below,
Service Layer code :-
OrderInfo orderInfo = new OrderInfo();
orderInfo.setParentOrderNumber("ORD123");
....
String param = objectMapper.writeValueAsString(orderInfo);
List<Order> list = jpaRepository.getByParentOrderNumber(param);
JpaRepository.java code :-
#Query(nativeQuery = true, value = "select * from thgcop_order_placement where order_info #> CAST(:condition as jsonb)")
List<Order> getByParentOrderNumber(#Param("condition") String parentOrderNumber);
This is how I achieve the result. I hope this will be very helpful for all enthusiastic!!
Thank you all for your help !!!
TL;DR: Make it work with Bind Parameter and plain JDBC first. Then move on to Spring Data, possibly falling back on a custom implementation.
You are facing problems on many levels here.
Let's start with ignoring Spring Data for now.
The statement you showed is very dissimilar from the one you try to construct with Spring Data because it doesn't contain a bind variable.
So instead of
SELECT * FROM thgcop_order_placement WHERE "order_info" #> '{"parentOrderNumber":"ORD123"}'
We should compare it to
SELECT * FROM thgcop_order_placement WHERE "order_info" #> '{"parentOrderNumber": ? }'
Note that we are losing the quotes since they denote a literal String but we aren't providing a literal String but a bind parameter.
I haven't found any indication that you can use bind parameters in parts of JSON expressions. So instead of the statement above we would need to use:
SELECT * FROM thgcop_order_placement WHERE "order_info" #> ?
Of course, the bind parameter should then contain the complete JSON expression
Unfortunately, this doesn't seem to work either because now Postgres considers the bind parameter a VARCHAR instead of a JSON expression. See https://blog.2ndquadrant.com/processing-json/. I think the correct version should be
SELECT * FROM thgcop_order_placement WHERE "order_info" #> ?::json
But I couldn't get this to work either.
In any case, you are left to transform your parameter to the JSON structure.
Normally I'd suggest using a SpEL expression for this. But it won't work because Spring Data chokes on the curly braces needed in the SpEL expression and considers them the end of the SpEL expression.
If you get something like this to work with a simple JDBC connection or JdbcTemplate you can start to think about #Query annotations.
#Query(
value= "SELECT * FROM thgcop_order_placement WHERE \"order_info #> :name::json",
nativeQuery = true)
This might trigger more problems since Spring Data will either consider ::json part of the parameter name. If this is the case you'll have to fall back on custom implementations.
I ran a couple of experiments, which you can look at and play with here.
Try to bind parameters as following
#Query(nativeQuery = true, value = "SELECT * FROM thgcop_order_placement"
+ " WHERE \"order_info\" #> '{\"parentOrderNumber\":\" ?1 \"}'")
I was stuck at the same problem for a while as well. It seems that springboot messes up while parsing the query present in string format. However this is the solution that I found, which will work directly as a native query in the repository:
Since order_info is of the type jsonb, the value being searched can be casted as a jsonb value.
Value to be searched: {"parentOrderNumber":"ORD123"}
Let's escape the whole string to be parsed by java.
String searchString = "{\"parentOrderNumber\":\"ORD123\"}"
Now, let's type the postgres query in a manner that spring will understand.
#Query(
value = "SELECT * from thgcop_order_placement where ((?1\\:\\:jsonb) <# (order_info\\:\\:jsonb))",
nativeQuery=true
)
List<Order> getByParentOrderNumber(String searchString);
Where,
Spring will replace ?1 with the value of searchString, as defined above.
:: is the typecast operator using which, we are explicitly typecasting the passed parameter (searchString) into jsonb. Therefore the value {\"parentOrderNumber\":\"ORD123\"} is converted into jsonb before an attempt is made for a search.
Also the values of column order_info is explicitly typecasted into jsonb.
Now, when both items (value to be searched and the column) are the same data type, we can use the <# operator to check if the search string is contained in the column values.
At the service level we just have to do this:
String orderNumber = "-- some order value e.g. ORD123 --"
String searchString = "{\"parentOrderNumber\":\"" + orderNumber + "\"}"
List<Order> list = jpaRepository.getByParentOrderNumber(searchString);
More details on Postgres JSON operators can be found in the official documentation HERE: https://www.postgresql.org/docs/9.5/functions-json.html

Resources