My understanding is, that with Spring data JPA I cannot have a query method to fetch all rows where a column equals a given non-null method parameter and use the same method to fetch all rows where this column is NULL when the method parameter is null.
Is that correct?
So I have to distinguish this in my JAVA code and I must use a separate query method explicitly asking for null values, like in the example below?
// Query methods
List<Something> findByParameter(Parameter parameter);
List<Something> findByParameterIsNull();
...
List<Something> result = new ArrayList<>();
if (parameter == null)
result = findByParameterIsNull();
else
result = findByParameter(parameter);
That's bad, if I have 4 parameters which could be null and would have to code 16 different query methods.
You are right.
A request has been made to support better handling of null parameters.
https://jira.spring.io/browse/DATAJPA-121
In your case, i would advise you to write your repository implementation and to use a custom CriteriaQuery to handle your case.
Also you can use the #Query annotation with the is null syntax :
#Query("[...] where :parameter is null"
public List<Something> getSomethingWithNullParameter();
EDIT
Since Spring data jpa 2.0, spring now supports #Nullable annotation. This can be helpful to handle null parameters passed.
From the documentation :
#Nullable – to be used on a parameter or return value that can be null.
i found something...if u put the parameter in the jpa method like this
#Param("value") String value,
then it can be null and in the query you will have this condition:
(table.value = :value OR :value IS NULL)
if the value is null it will automatically return true and if is not null, it will search that value in the table.
It seems Query by Example might be what you need.
Query by Example is a new feature in Spring Data (since version Hopper, out April 2016), which allows one to create simple dynamic queries with a code like this
Person person = new Person();
person.setFirstname("Dave");
ExampleMatcher matcher = ExampleMatcher.matching()
.withIncludeNullValues();
Example<Person> example = Example.of(person, matcher);
personRepository.count(example);
personRepository.findOne(example);
personRepository.findAll(example);
Methods count/findOne/findAll that take an instance of org.springframework.data.domain.Example as a parameter (and some of them also take sorting/pagination parameters) are coming from org.springframework.data.repository.query.QueryByExampleExecutor<T> interface, which is extended by org.springframework.data.jpa.repository.JpaRepository<T, ID extends Serializable> interface.
In short, all JpaRepository instances now have these methods.
Today as of Jun 2018, by looking at https://jira.spring.io/browse/DATAJPA-121, the query will automatically form is null if your parameter is null.
I did that in my project, it is true:
compile group: 'org.springframework.data', name: 'spring-data-jpa', version: '2.0.7.RELEASE'
--
public interface AccountDao extends CrudRepository<T, ID> {
//this can accept null and it will become isNull
public List<MyAccount> findByEmail(String email);
}
if parameter is null:
select
myaccount0_.id as id1_0_,
myaccount0_.email as email2_0_,
myaccount0_.password as password3_0_,
myaccount0_.user_id as user_id4_0_
from
my_account myaccount0_
where
myaccount0_.email is null
if parameter is not null:
select
myaccount0_.id as id1_0_,
myaccount0_.email as email2_0_,
myaccount0_.password as password3_0_,
myaccount0_.user_id as user_id4_0_
from
my_account myaccount0_
where
myaccount0_.email=?
11:02:41.623 [qtp1507181879-72] TRACE o.h.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [testing#hotmail.com]
Then it comes to an interesting question, some developers want better control to ignore the parameter in query if it is null, this is still being under investigating in https://jira.spring.io/browse/DATAJPA-209.
In my case membershipNumber is nullable, and I have handled it this way. This will handle all the cases where table.membershipNumber is null too.
#Query(value = "SELECT pr FROM ABCTable pr " +
"WHERE LOWER(pr.xyz) = LOWER(:xyz) " +
"and LOWER(pr.subscriptionReference) = LOWER(:subscriptionReference) " +
"and pr.billId = :billId " +
"and ((pr.membershipNumber = :membershipId) or (pr.membershipNumber = null and :membershipId = null))")
List<PaymentRequest> getSomething (#Param("xyz") String xyz,
#Param("subscriptionReference") String subscriptionReference,
#Param("billId") Integer billId,
#Param("membershipId") String membershipNumber);
While this has been answered and the accepted answer is relevant to the current question but there is another way to handle your null parameters in a JpaRespository. Posting this here as this can be leveraged when someone wants to query by ignoring fields when null and have dynamic query built.
The below code sample should demonstrate the same
public class User{
private String firstName;
private String lastName;
}
import javax.persistence.criteria.Predicate;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User,Long>{
public Page<AppUser> findAll(Specification<AppUser> user,Pageable page);
public default Page<AppUser> findAll(User user,Pageable page){
return findAll(search(user),page);
}
static Specification<User> search(User entity) {
return (root, cq, cb) -> {
//To ensure we start with a predicate
Predicate predicate = cb.isTrue(cb.literal(true));
if(entity.getFirstName() != null && !entity.getFirstName().isBlank()) {
Predicate _predicate = cb.like(cb.lower(root.get("firstName")), "%"+entity.getFirstName().toLowerCase()+"%");
predicate = cb.and(predicate,_predicate);
}
if(entity.getLastName() != null && !entity.getLastName().isBlank()) {
Predicate _predicate = cb.like(cb.lower(root.get("lastName")), "%"+entity.getLastName().toLowerCase()+"%");
predicate = cb.and(predicate,_predicate);
}
return predicate;
}
}
}
I was able to apply IS NULL appropriately in case of null input using below workaround.
#Query("SELECT c FROM ConfigRLLOAContent c WHERE ((:suffixId IS NOT NULL AND c.suffixId = :suffixId) OR (:suffixId IS NULL AND c.suffixId IS NULL))")
Optional<ConfigRLLOAContent> findByRatableUnitId(#Param("suffixId") String suffixId);
Above approach will apply filters only when suffixId is non-null,
else, IS NULL filter will be applied.
There's also an issue raised on github, to which introduction of #NullMeans is proposed here.
I had the same issue with similar task - one parameter in the query was optional, so to get rid of this error, I managed to use the following query with 2 casts:
#Query(value = "select distinct name from table "
+ "where coalesce(cast(table.field_optional as text) = cast(?1 as text), true) "
+ "and lower(table.non_optional_field) like ?2 "
+ "limit ?3", nativeQuery = true)
List<String> method(String optionalParam, String param, int limit);
This coalesce part would transform into simple 'true' if optionalParam is null
Related
I have several custom queries in an interface that extends JpaRepository. The interface is analogous to the below (note: the below is just for illustration, and may have errors, but don't concern yourself with that).
public interface MyRepo extends JpaRepository<SMark, String> {
#Transactional
#Modifying
#Query(value = "INSERT INTO my_table(id, col_1, col_2) " +
"values(:col_1, :col_2))", nativeQuery = true)
int insertRecord(#Param("col_1") String col_1, #Param("col_2") String col_2);
So, my issue is that I am finding it difficult to do anything useful with the int return type for anything other than a successful query (which will return a 1). Is there a way to do anything useful with the return other than sending the value as part of the response? In other words, if the response is not a 1, and an exception is not thrown, can the non-1 response be translated into something more informative to the user?
For example, I am currently doing the following, which I would like to improve upon if I was a confident about the not-1 status:
if(status == 1) {
StatusResponse statusResponse = new StatusResponse("Successful Delete ", null);
return new ResponseEntity<>(statusResponse, HttpStatus.OK);
}
else {
StatusResponse statusResponse = new StatusResponse("Delete not successful (lacking details from response) ", null);
return new ResponseEntity<>(statusResponse, HttpStatus.NOT_ACCEPTABLE);
}
Grateful for any response. Thanks!
I would not recommend the approach of using return type as valid operation or not. I would prefer having database level constraint like unique constraint, check constraint or trigger check for insert/update/delete.
Nevertheless, we can use default method inside interface and throw database exception and in Spring you can configure ExceptionHandler to wrap the exception and return with some valid error code ?
public interface MyRepo extends JpaRepository<SMark, String> {
#Transactional
#Modifying
#Query(value = "INSERT INTO my_table(id, col_1, col_2) " +
"values(:col_1, :col_2))", nativeQuery = true)
int insertRecord(#Param("col_1") String col_1, #Param("col_2") String col_2);
default void insert(String col1, String col2) {
if (insertRecord(col1, col2) != 1) {
throw new DataIntegrityViolationException("Unable to perform DML operation.");
}
}
}
Since the return value of this methods indicates the number of modified objects (in your can it can only be 0 or 1) I would just use it to translate it into a more understandable response.
The simplest case is a REST Api:
When the Method returns 1 the POST/PUT/PATCH call was successful (if there was no other error) and you would return 201 Created and maybe a little more like a Location header.
And when it returns 0 it means no object got modified and because it's an insert this shouldn't happen and therefore you would return 500 InternalServerError
But I would say in your case this is redundant because as I said it's an insert , you only have those two return options and I guess if something doesn't work during the insert you already get an exception by spring boot and therefore when you called the method without getting an error I would say it was successful.
But ofcourse you can double check to be sure and maybe even use it for tests to enforce the expected behavior or something else.
What I have is a Spring Boot repository in which I try to create a query function using HQL.
The function takes an Integer parameter which should be ignored by the query if it is null, else a list should be checked if it contains the value.
I have the where clause in two parts, the null check and the list check, looking like this:
#Query("SELECT u FROM MyEntity " +
" WHERE (:myParam is null or :myParam in (2, 3))"
)
Now the problem is that for the :myParam in (2, 3) part it is complaining "Inconsistent Datatypes: expected BINARY got NUMBER
(when :myParam is null, for :myParam != null it works)
I tried:
casting either the param or the null value to in
using coalesce(:myParam, CAST(NULL AS int)) which worked on a similar problem with :myParam being an integer list
using a switch case statement
(case when :spracheKy is null then true when :spracheKy in (2, 3) then true else false end) = true
Thanks in advance for any help
why don't you just make use of two different repository methods, e.g. one that takes no parameter and another method that takes the parameter. and then decide and encapsulate the decision taking logic in a separate method of your service layer - I mean the logic which repository method to call based on parameter being null or not... could look like this:
#Service
#Transactional
public class YourService {
// autowired by constructor injection
private final YourEntityRepository repo;
public List<YourEntity> getAllYourEntitiesByParam(Long param) {
if (param == null) {
return repo.findAll();
}
return repo.findAllByParam(param);
}
}
I'm building a repository with a getAll query that uses a complex type parameter to filter, like:
#Query("""
SELECT c FROM City c
WHERE :filter IS NULL OR c.code LIKE %:#{#filter.code}%
""")
fun getAllCitiesFiltered(#Param("filter") filter: MyFilter?) : List<City>
The MyFilter class is a simple POJO:
class MyFilter {
var code: String? = null
var description: String? = null
}
At some point in my code I call getAllCitiesFiltered(filter) and this filter may be null or may have one of it's properties set to null. I have two questions:
How can I deal with the fact that filter is nullable? The way it is now (like above), whenever a null value is passed to it I get an exception EL1007E: Property or field 'code' cannot be found on null
Is there a less ugly way in HQL to deal with the fact the the properties code and description may be null and when they are null I don't want to filter by them? The only way I think of now is doing things like ... WHERE filter.code IS NULL or filter.code LIKE %c.code%
I'm new to JPA and I'm not sure if using #Query is the best approach here. I'm open to suggestions to change that as well.
Thanks!
EDIT after Alan Hay suggestion
I'm using QueryDSL and, coming from C#/LINQ background, finding it great.
The "problem" is that I'm doing things like this:
val city = QCity.city
var query = JPAQuery<City>(entityManager).from(city)
if (filter?.code != null)
query = query.where(city.code.eq("BH"))
if (filter?.description != null)
query = query.where(city.description.eq("Belo Horizonte"))
Is there a better way of writing this other than switching and if/else'ing?
Thanks!
The best way is to use interface default method :
interface CityJpaRepository : JpaRepository<City, Int> {
fun findByCodeLike(code: String) : List<City>
fun findByFilter(filter: MyFilter?) : List<City> {
return filter?.code?.let { findByCodeLike(it.code) } ?: findAll()
}
}
I am trying to get data from the Spring Data JPA repository which throws JPQL with sort column. This column corresponds to fields with the OneToMany annotation.
In any value that has null in that field does not exist in the result.
This behaviour exists only when data requests to throw JPQL. If I construct the same query throw, Specification, and call repository.findAll(specification, paggeble), everything works fine.
In this post, Spring-data JPA repository Order by losing null values in results, it says that the bug fix is in Spring Data starting from version 1.2.1. But I tried on version 1.11.13 and the bug is still there.
Piece of code for better understanding:
#Repository
public interface MyEntityRepository extends JpaRepository<MyEntity, Integer>, JpaSpecificationExecutor<MyEntity> {
#Query(value =
"SELECT e "
+ " FROM MyEntity e "
+ " WHERE e.flg = :flg "
+ "";)
Page<MyEntity> findByFlg(
Pageable pageable
);
}
public class Service {
//This method return full result.
//If field dept.someField is null MyEntity obj still exists in page
public Page<MyEntity> findDataSpec(Pageable pageable) { //sort[dept.someField:ASC]
Specification<MyEntity> specification = (root, criteriaQuery, criteriaBuilder) -> {
Path flg = root.get("flg");
return criteriaBuilder.equal(flg, "1");
};
Page<MyEntity> page = myEntityRepository.findByDeptAndTpAndKnd(specification, pageable);
return page;
}
//This method return wrong result.
//If field dept.someField is null MyEntity obj escape from page
public Page<MyEntity> findDataJpql(Pageable pageable) {//sort[dept.someField:ASC]
Page<MyEntity> page = myEntityRepository.findByFlg("1", pageable);
return page;
}
}
May someone explain why two identical queries work so different and which of these work wrong.
What I need to do for JPQL query is have the same behavior like specification behavior.
I have this method signature:
public int nrOfEntities(Class<? extends MailConfirmation> clazz, User user, String email)
I would like nrOfEntities to return the number of entities that:
Are of the concrete class clazz
Have a matching User if user != null
Have a matching email if user == null
It's the class matching I'm having a problem with. I've tried a few statements without any luck.
Can clazz have subtypes that should not be counted?
If not, is it not sufficient to create the query on clazz?
Criteria criteria = session.createCriteria(clazz);
if (user == null) {
criteria.add(Restrictions.eq("email", email);
} else {
criteria.add(Restrictions.eq("user", user);
}
int result = (Integer) criteria.setProjection(Projections.rowCount()).uniqueResult();
Now I am guessing how your mapping looks (that there are "email" and "user" properties).
If that is not working, I know that there is a pseudo property named "class", at least in HQL. Maybe you can experiment with that.
Are you looking for "from " + clazz.getSimpleName() + " where ..."?
If you want to test the class of an object, you should be able to use something like the following:
Object entity = ... // Get the entity however
boolean matchesClass = entity.getClass().equals(clazz);
If this isn't working for you, give some examples of how it fails since it should be this straightforward!