How can I place a n1ql query with variables in a spring #Query annotation? - spring

I have the following N1QL query:
UPDATE `bucket`
SET b.terms.min_due.`value` = "12345" FOR b IN balances END
WHERE entry_id = "12345"
I want to place it in an #Query annotated repository method, but the values for value and entry_id need to be variable.
They are for a patch operation that updates only parts of the document. I have to do it using inline N1ql via #Query, so it is the only thing I've tried.
#Query(UPDATE `bucket` SET b.terms.min_due.`value` = "12345" FOR b IN balances END WHERE entry_id = "12345")
<T> Mono<T> patch(T Aggregate);
I want to construct a repository method that replaces "12345" for value and entry_id with values pulled from the aggregate, then executes the N1ql query, updating only the value specified in the statement instead of the whole couchbase document.

You can use the standard spEL syntax for that:
#Query("#{#n1ql.selectEntity} where #{#n1ql.filter} and companyId = $2 and $1 within #{#n1ql.bucket}")
BusinessUnity findByAreaRefId(String areaRefId, String companyId);

Related

Spring data JDBC custom query

I have defined the following custom database query.
#Query("select po.* from purchase_order po where po.purchase_order_number = :purchaseOrderNumber")
List<PurchaseOrder> findByPurchaseOrderNumber(String purchaseOrderNumber);
This query always return null regardless of the value of purchas. On the other hand, if I replace the dynamic parameter purchaseOrderNumber in the query with a hard-coded value (as the one depicted below) it perfectly works.
#Query("select po.* from purchase_order po where po.purchase_order_number = "10")
List<PurchaseOrder> findByPurchaseOrderNumber(String purchaseOrderNumber);
I would appreciate it if someone can help me to understand why my query with the dynamic PurchaseOrderNumber doesn't work?
Specify the name of the query parameter
#Query("select po.* from purchase_order po where po.purchase_order_number = :purchaseOrderNumber")
List<PurchaseOrder> findByPurchaseOrderNumber(#Param("purchaseOrderNumber") String purchaseOrderNumber);

Spring JPA repository method to get sorted distinct and non-null values

To get distinct data based on multiple columns and exclude NULL values on a column and sort the result in SQL, I would write query like:
SELECT DISTINCT CAR_NUMBER, CAR_NAME
FROM CAR
WHERE CAR_NUMBER IS NOT NULL
ORDER BY CAR_NUMBER
This would return me rows with distinct values for CAR_NUMBER and CAR_NAME and it would exclude any rows having CAR_NUMBER = NULL and finally, it would sort the result by CAR_NUMBER.
However, In Spring JPA, I gather you can use either methods named based on your entity fields or using #Query annotation.
I am trying to do this:
List<Car> findDistinctByCarNumberAndCarNameAndCarNumberIsNotNull(Sort sort);
, and to call this method like:
myRepo.findDistinctByCarNumberAndCarNameAndCarNumberIsNotNull(Sort.by("carNumber"));
but this is failing on Maven > Install with error like "findDistinctByCarNumberAndCarNameAndCarNumberIsNotNull(Sort sort) expects at least 1 arguments but only found 0".
Similarly, I tried using #Query like below but with same effect:
#Query(SELECT DISTINCT c.carNumber, c.carName FROM carEntity c WHERE c.carNumber IS NOT NULL ORDER BY c.carNumber)
List<Car> findAllCars();
I figured out the problem. Following is how I solved it:
In my repository:
#Query("select distinct c.carNumber, c.carName from CarEntity c where c.carNumber is not null")
List<Object> findAllDistinctRegions(Sort sort);
Important here to realize is that #Query returns List<Object>, not List<Car>.
Next, in my service, call this method:
List<Object> carData = carRepository.findAllDistinctCars(Sort.by("carNumber"));
That worked finally fine; however, I run into another problem where I had to do necessary conversion from List to List.
// This is bit tricky as the returned List<Object> is actually
// List<Object[]>. Basically, each field returned by the #Query
// is placed into an array element.
//To solve it, I had to do following:
List<Car> cars = new ArrayList<Car>();
for(Object data: carsData) {
Object[] obj = (Object[]) data;
cars.add(new CarDto((Short) obj[0], ((String) obj[1]));
}
I just remembered there is a better way to solve this than that helper function that you described in your answer and thought I would share it.
Projection in JPQL would be a cleaner way to create your DTO:
#Query("SELECT DISTINCT new com.yourdomain.example.models.MyDto(c.carNumber, c.carName)
FROM CarEntity c WHERE c.carNumber is not null")
List<CarDto> findAllDistinctRegions(Sort sort);

Spring And Kotlin Query

How can I achieve this query
select *
from table t
where name like '%Ami%'
order by (name = 'Ami') desc, length(col);
(just the sort part)
Using springframework Sort..
What I tried is
Sort.by(Sort.Direction.DESC, "name") // But I need to sort by name = 'Ami'
Sort.by(Sort.Direction.DESC, "name" = 'Ami'") // throws an error
JpaSort.unsafe(Sort.Direction.DESC, "name" = 'Ami'") // throws an error
Looks like the documentation has an example almost identical to your question:
https://docs.spring.io/spring-data/jpa/docs/2.4.5/reference/html/#jpa.query-methods.sorting
However, using Sort together with #Query lets you sneak in
non-path-checked Order instances containing functions within the ORDER
BY clause. This is possible because the Order is appended to the given
query string. By default, Spring Data JPA rejects any Order instance
containing function calls, but you can use JpaSort.unsafe to add
potentially unsafe ordering.

Null and Empty Check for a IN Clause parameter for Spring data jpa #query?

My Spring data JPA code to get the data from db based on some search criteria is not working. My DB is SQL Server 2012, same query seem to work with MYSQL DB.
Code Example :
#Query(value = "select * from entity e where e.emp_id=:#{#mySearchCriteria.empId} and ((:#{#mySearchCriteria.deptIds} is null or :#{#mySearchCriteria.deptIds} ='') or e.dept_id in (:#{#mySearchCriteria.deptIds})) ", nativeQuery = true)
public List<Entity> search(#Param("mySearchCriteria") MySearchCriteria mySearchCriteria);
if list mySearchCriteria.deptIds has more than one value- it's not working(it's actually translating it to wrong query. Any lead? Thanks in advance.
Note: data type for deptIds is List of Integer
Its complaining because values of {#mySearchCriteria.deptIds} is comma separated list e.g. 'Value1', 'Value2' so the query gets translated as ('Value1', 'Value2' is null) which causes this error.
Need to verify if list is empty or not and then change the query with IN clause and one without IN clause.
Surround the list by parentheses. This works for me.
(:#{#mySearchCriteria.deptIds}) is null

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