I am working on a project involving Spring
and JPA/Hibernate
. The database driver used in my development environment is H2
. My application has a page that displays statistics, one example of such a statistic is the average age of my users. However, when I try to fetch the average age using JPQL
, I receive an exception
Result must not be null!
Assume for simplicity reasons that I store age as an integer
on every User
object (in my application this is of course not the case, but that's not important for my problem).
User model
@Entity
public class User implements Identifiable<Long> {
private int age;
// more fields and methods, irrelevant
}
User repository
@Repository
public interface UserRepository extends CrudRepository<User, Long> {
@Query("SELECT AVG(u.age) FROM #{#entityName} u")
long averageAge();
}
I cannot seem to figure out why calling UserRepository#averageAge();
is throwing the exception. I have tried replacing the function AVG
in the query by COUNT
and this behaves as expected. I have also tried to use an SQL query and setting nativeQuery = true
in the annotation, yet to no avail. I can ofcourse solve it by fetching all the users and calculate the average age in plain Java, but this wouldn't be very efficient.
Stacktrace:
Caused by: org.springframework.dao.EmptyResultDataAccessException: Result must not be null!
at org.springframework.data.repository.core.support.MethodInvocationValidator.invoke(MethodInvocationValidator.java:102)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
at com.sun.proxy.$Proxy150.averageAge(Unknown Source)
at my.test.application.StatisticsRunner.run(StatisticsRunner.java:72)
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:809)
... 30 more
Solved
The exception was caused by the fact that AVG()
returns null
when performed on an empty table. I fixed it by modifying the query (inspired by the answer to this question) as follows:
@Query("SELECT coalesce(AVG(u.age), 0) FROM #{#entityName} u")
long averageAge();
The normal behavior is indeed returning an empty list if no results are found. If a List<Object> is the return value of the method in the defined interface, the method should never return Null .
CrudRepository provides CRUD functions. PagingAndSortingRepository provides methods to do pagination and sort records. JpaRepository provides JPA related methods such as flushing the persistence context and delete records in a batch.
The solution is to use @javax. persistence. Version on a new versionNumber column in all the tables. If you have a parent and child table then use @Version column in all the entity classes.
Its findById method retrieves an entity by its id. The return value is Optional<T> . Optional<T> is a container object which may or may not contain a non-null value. If a value is present, isPresent returns true and get returns the value.
If you use Spring Data, and if your method returns null
when Hibernate can't find a match, make sure you add @org.springframework.lang.Nullable
to your method signature:
public interface SomeRepositoryCustom {
@org.springframework.lang.Nullable
public Thing findOneThingByAttr(Attribute attr) {
/* ...your logic here... */
}
}
This is because Spring Data checks the nullability of your method, and if the annotation is missing, it's going to enforce that you always need to return an object:
/* org.springframework.data.repository.core.support.MethodInvocationValidator */
@Nullable
@Override
public Object invoke(@SuppressWarnings("null") MethodInvocation invocation) throws Throwable {
/* ...snip... */
if (result == null && !nullability.isNullableReturn()) {
throw new EmptyResultDataAccessException("Result must not be null!", 1);
}
/* ...snip... */
I used Spring Boot version 2.1.1.RELEASE
and Spring Data 2.1.4.RELEASE
.
It seems that the EmptyResultDataAccessException
exception is thrown when a result from a query was expected to have at least one row (or element) but none was returned.
Related documentation about this can be found here.
I would suggest to run the same query this attempts to run in order to further validate this theory. Now the good question's what to do with this.
You have two options. Either catch the EmptyResultDataAccessException
exception at your calling point and handle it directly in there or alternatively you can have an ExceptionHandler
which will be tasked with handling such exceptions.
Both ways of handling this, should be OK and you may choose between each depending on your scenario.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With