I have a JPA query
#Query(value = "SELECT SUM(total_price) FROM ... WHERE ...", nativeQuery = true)
which works as expected when there are matching records. But when there are no matching records, the query returns null.
How can I return zero(0) instead of null when no records are found?
You can change return type to be an Optional;
#Query(value = "SELECT SUM(total_price) FROM ... WHERE ...", nativeQuery = true)
Optional<Integer> getSum(...);
Or you can wrap this getSum() with a default method;
#Query(..)
Integer getSum(...);
default Integer safeGetSum(..) {
return Optional.ofNullable(getSum(..)).orElse(0);
}
More info on null handling in repositories
When the return value is not a list, or some wrapper (plus some others check below), & there are no matching records, the return will be null, so there is no slick way to handle this with some defaultValue=0 through #Query
The absence of a query result is then indicated by returning null. Repository methods returning collections, collection alternatives, wrappers, and streams are guaranteed never to return null but rather the corresponding empty representation.
A native SQL option is COALESCE which returns the first non-null expression in the arg list
SELECT COALESCE(SUM(total_price),0) FROM ...
Related
I am using Spring JPA in my application to fetch certain records from the DB. Now, one of the query parameters that I am passing can be null in certain criteria. So I have designed the query in such a way that if the query parameter is not null, then the query parameter is used for extraction otherwise it is ignored.
Query
#Query(value = "SELECT * FROM fulfilment_acknowledgement WHERE entity_id = :entityId " +
"and item_id = :itemId " +
"and (fulfilment_id is null OR :fulfilmentId is null OR fulfilment_id = :fulfilmentId) " +
"and type = :type", nativeQuery = true)
FulfilmentAcknowledgement findFulfilmentAcknowledgement(#Param(value = "entityId") String entityId, #Param(value = "itemId") String itemId,
#Param(value = "fulfilmentId") Long fulfilmentId, #Param(value = "type") String type);
NOTE: The type of fulfilment_id in the table fulfilment_acknowledgement is int8. It is a Postgres RDS.
Now, if I encounter a scenario where the fulfilmentId is actually blank, I am getting the below error:
2022-06-23 15:31:56,997 89645 [boundedElastic-5] DEBUG org.hibernate.SQL - SELECT * FROM fulfilment_acknowledgement WHERE entity_id = ? and item_id = ? and (fulfilment_id is null OR ? is null OR fulfilment_id = ?) and type = ?
2022-06-23 15:31:57,154 89802 [boundedElastic-5] DEBUG o.h.e.jdbc.spi.SqlExceptionHelper - could not extract ResultSet [n/a]
Exception while creating Fulfilment Acknowledgement: [could not extract ResultSet; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not extract ResultSet]
Caused by: org.hibernate.exception.SQLGrammarException: could not extract ResultSet
Caused by: org.postgresql.util.PSQLException: ERROR: operator does not exist: bigint = bytea
Hint: No operator matches the given name and argument types. You might need to add explicit type casts.
I have a solution in mind where I can update the fulfilmentId to a default value like -1 if it is null, but I need to understand why is it failing? What am I missing here?
I was running into similar issue for a native query, which find records with matching column values. When the value for any parameter is null, hibernate uses the wrong type throws casting exceptions. After spending some time I found that in such cases need to use Hibernate's TypedParameterValue
So in your case #Param(value = "fulfilmentId") Long fulfilmentId would be
#Param(value = "fulfilmentId") TypedParameterValue fulfilmentId
Also from the service you need to convert your parameter accordingly from Long to TypedParameterValue as below:
TypedParameterValue fulfilmentIdParam = new TypedParameterValue(StandardBasicTypes.LONG, fulfilmentId);
I have a JPA/Hibernate entity which has a JSONB column (using https://github.com/vladmihalcea/hibernate-types ) for storing a list of strings. This works fine so far.
#TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
#Type(type = "jsonb")
#Column(name = "TAGS", columnDefinition = "jsonb")
private List<String> tags;
Now I want to check if another string is contained in the list of strings.
I can do this by writing a native query and use the #> operator from Postgres. Because of other reasons (the query is more complex) I do not want to go in that direction. My current approach is calling the jsonb_contains method in a Spring Data specification (since the operator is just alias to this function), e.g. jsonb_contains('["tag1", "tag2", "tag3"]','["tag1"]'). What I am struggling with is, getting the second parameter right.
My initial approach is to also use a List of Strings.
public static Specification<MyEntity> hasTag(String tag) {
return (root, query, cb) -> {
if (StringUtils.isEmpty(tag)) {
return criteriaBuilder.conjunction();
}
Expression<Boolean> expression = criteriaBuilder.function("jsonb_contains",
Boolean.class,
root.get("tags"),
criteriaBuilder.literal(List.of(tag)));
return criteriaBuilder.isTrue(expression);
};
}
This results in the following error.
Caused by: org.postgresql.util.PSQLException: ERROR: function jsonb_contains(jsonb, character varying) does not exist
Hinweis: No function matches the given name and argument types. You might need to add explicit type casts.
Position: 375
It does know that root.get("tags") is mapped to JSONB but for the second parameter it does not. How can I get this right? Is this actually possible?
jsonb_contains(jsob, jsonb) parameters must be jsonb type.
You can not pass a Java String as a parameter to the function.
You can not do casting in Postgresql via JPA Criteria.
Using JSONObject or whatever does not help because Postgresql sees it as
bytea type.
There are 2 possible solutions:
Solution 1
Create jsonb with jsonb_build_object(text[]) function and send it to jsonb_contains(jsonb, jsonb) function:
public static Specification<MyEntity> hasTag(String tag) {
// get List of key-value: [key1, value1, key2, value2...]
List<Object> tags = List.of(tag);
// create jsonb from array list
Expression<?> jsonb = criteriaBuilder.function(
"jsonb_build_object",
Object.class,
cb.literal(tags)
);
Expression<Boolean> expression = criteriaBuilder.function(
"jsonb_contains",
Boolean.class,
root.get("tags"),
jsonb
);
return criteriaBuilder.isTrue(expression);
}
Solution 2
Create custom function in your Postgresql and use it in Java:
SQL:
CREATE FUNCTION jsonb_contains_as_text(a jsonb, b text)
RETURNS BOOLEAN AS $$
SELECT CASE
WHEN a #> b::jsonb THEN TRUE
ELSE FALSE
END;$$
LANGUAGE SQL IMMUTABLE STRICT;
Java Code:
public static Specification<MyEntity> hasTag(String tag) {
Expression<Boolean> expression = criteriaBuilder.function(
"jsonb_contains_as_text",
Boolean.class,
root.get("tags"),
criteriaBuilder.literal(tag)
);
return criteriaBuilder.isTrue(expression);
}
I think that the reason is that you pass the varchar as the second param. jsonb_contains() requires two jsonb params.
To check a jsonb array contains all/any values from a string array you need to use another operators: ?& or ?|.
The methods bindings for them in PSQL 9.4 are: jsonb_exists_all and jsonb_exists_any correspondingly.
In your PSQL version, you could check it by the following command:
select * from pg_operator where oprname = '?&'
I am trying to build the CriteriaQuery equivalent of the following SQL where clause, using OpenJPA 2.4.0:
where employee_status is null or employee_status not in ('Inactive','Terminated')
I have created a list of employeeStatuses, and this is how I am adding the predicate:
predicates.add(
criteriaBuilder.or(
criteriaBuilder.isNull(domainEntity.get("employeeStatus")),
criteriaBuilder.not(domainEntity.get("employeeStatus").in(employeeStatuses))
)
);
The generated SQL looks like this:
AND (t0.EMPLOYEE_STATUS IS NULL OR NOT (t0.EMPLOYEE_STATUS = ? OR t0.EMPLOYEE_STATUS = ?) AND t0.EMPLOYEE_STATUS IS NOT NULL)
As you can see, the 'not in' statement is being transformed into multiple comparisons, with 't0.EMPLOYEE_STATUS IS NOT NULL' added at the end. In order for this to work, the translated clause should be contained in another set of parenthesis.
Any ideas about how I can get this to work?
I am struggling with JPQL dynamic where condition. I tried searching the syntax for the same but coluldn't find one.
in my case if user is passing the name parameter then the select query should be
select * from user where name = 'sanjay'
if user is not passing name parameter then select query should be
select * from user
Below is my jpql query format which fails when name parameter is not passed.
entity_manager.createQuery("select u from user u where u.name = :name").setParameter("name",params[:name]).getResultList()
How can i update above JPQL query to support both the cases i.e when the name parameter is passed and when the name parameter is not passed ??
This is not possible in JPQL. You even cannot do something like
createQuery("select u from user u where u.name = :name OR :name IS NULL")
It is not possible. That simple. Use two queries or use the Criteria API.
This is the answer I get when I tries to do like you it is working with some modification.
In my case I had the problem that my optional parameter was a List<String> and the solution was the following:
#Query(value = "SELECT *
FROM ...
WHERE (COLUMN_X IN :categories OR COALESCE(:categories, null) IS NULL)"
, nativeQuery = true)
List<Archive> findByCustomCriteria1(#Param("categories") List<String> categories);
This way:
If the parameter has one or more values it is selected by the left side of the OR operator
If the parameter categories is null, meaning that i have to select all values for COLUMN_X, will always return TRUE by the right side of the OR operator
Why COALESCE and why a null value inside of it?
Let's explore the WHERE clause in all conditions:
Case 1: categories = null
(COLUMN_X IN null OR COALESCE(null, null) IS NULL)
The left part of the OR will return false, while the right part of the OR will always return true, in fact COALESCE will return the first non-null value if present and returns null if all arguments are null.
Case 2: categories = ()
(COLUMN_X IN null OR COALESCE(null, null) IS NULL)
JPA will automatically identify an empty list as a null value, hence same result of Case 1.
Case 3: categories = ('ONE_VALUE')
(COLUMN_X IN ('ONE_VALUE') OR COALESCE('ONE_VALUE', null) IS NULL)
The left part of the OR will return true only for those values for which COLUMN_X = 'ONE_VALUE' while the right part of the OR will never return true, because it is equals to 'ONE_VALUE' IS NULL (that is false).
Why the null as second parameter? Well, that's because COALESCE needs at least two parameters.
Case 4: categories = ('ONE_VALUE', 'TWO_VALUE')
(COLUMN_X IN ('ONE_VALUE', 'TWO_VALUE') OR COALESCE('ONE_VALUE', 'TWO_VALUE', null) IS NULL)
As in Case 3, the left part of the OR operator will select only the rows for which COLUMN_X is equale to 'ONE_VALUE' or 'TWO_VALUE'.
Using spring data JPA, I am trying to make this sort of query (it is more complex, this is a simple case)
#Query(nativeQuery = true,
value = "SELECT * FROM events WHERE typeId IN (?1)")
List<Event> findEventsByType(List<Integer> types);
When I launch the query, an exception raises:
org.postgresql.util.PSQLException: Can't infer the SQL type to use for an instance of java.util.ArrayList. Use setObject() with an explicit Types value to specify the type to use.
I have tried List < Integer >, Integer[], Object[] and String but it is not working...
Can't I pass list of values?
Which is the best approach to make this sort of queries?
Thanks!
Try taking away the #Query and make the method name:
public List<Event> findByTypeIn(List<Integer> types);
See table 2.2 in the link: http://docs.spring.io/spring-data/jpa/docs/1.2.0.RELEASE/reference/html/
I tried like below and it works for me.
#Query(value = "select * from events where type_id in :types", nativeQuery = true)
List<Event> findEventsByType(#Param("types") List<Integer> types);
#Query(value = "SELECT c from Company c where " +
"c.companyName IN (:company_names)")
List<Company> findCompaniesByName(#Param("company_names") List<String> companyNames);
This is the solution to your problem.
Here I am passing List which contains company names and I am querying DB and storing result in List.
Hope this hepls!
Use JPQL. A native query is or should be passed to the database exactly as you have created the SQL string, and unless your driver can take a serialized collection and understand that the single parameter needs to be interpreted as many, it just won't work. The collection you pass in needs the SQL expanded from (?) to (?, ?,...) based on the number of elements in a collection, and JDBC drivers just are not able to do this, and JPA providers are required to execute the string as is.
A JPQL query allows the JPA provider to create the SQL it needs dynamically based on the list passed in, so it can expand the collection for you.
Try this. It will work for Native Query in SpringBoot JPA:
#Query(value = "SELECT * FROM table WHERE columnName IN (:inputList)" ,
nativeQuery = true)
List<Object> findByObjectList(#Param("inputList") List<Object> inputList);
And also in case of JPA, try the below :
List<Object> findByObjectList(List<Object> inputList)
I know this is a little bit out of context (we use Update and not Select), but this can be usefull for others :
/**
* Update the state of list of entities using their ids
* #param ids request ids
* #param state new state
* #return
*/
#Modifying
#Query(value = "UPDATE AbstractRequest SET state = :state WHERE id IN (:ids)")
int updateStates(#Param("ids") List<Long> ids, #Param("state") InternalRequestStep state);
pass array this way inside IN Clause
#Query(value = "select name from teams where name in :names", nativeQuery = true)
List<String> getNames(#Param("names") String[] names);
Call this way
String[] names = {"testing team","development team"};
List<String> teamtest = teamRepository.getNames(names);
Remove brackets around (?1) parameter. Your query value should look like that:
#Query(value = "SELECT * FROM events WHERE typeId IN ?1")
try querying like this:
#Query(nativeQuery = true,
value = "SELECT * FROM events WHERE typeId = ?1")
List<Event> findEventsByType(List<Integer> types);
did it work?