简体   繁体   English

Spring Data JPA在空结果集上聚合函数

[英]Spring Data JPA aggregate functions on an empty resultset

I am working on a project involving Spring and JPA/Hibernate . 我正在开发一个涉及SpringJPA/Hibernate的项目。 The database driver used in my development environment is H2 . 我的开发环境中使用的数据库驱动程序是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 但是,当我尝试使用JPQL获取平均年龄时,我收到一个异常

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). 假设为了简单起见,我将age存储为每个User对象的integer (在我的应用程序中,当然不是这种情况,但这对我的问题并不重要)。

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(); 我似乎无法弄清楚为什么调用UserRepository#averageAge(); is throwing the exception. 抛出异常。 I have tried replacing the function AVG in the query by COUNT and this behaves as expected. 我已经尝试用COUNT替换查询中的函数AVG ,这表现得如预期的那样。 I have also tried to use an SQL query and setting nativeQuery = true in the annotation, yet to no avail. 我也尝试在注释中使用SQL查询并设置nativeQuery = true ,但无济于事。 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. 我可以通过获取所有用户并使用普通Java计算平均年龄来解决它,但这不会非常有效。

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. 异常是由AVG()在空表上执行时返回null引起的。 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();

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. 当查询的结果预计至少有一行(或元素)但没有返回时,似乎抛出了EmptyResultDataAccessException异常。

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. 在您的调用点捕获EmptyResultDataAccessException异常并直接在那里处理它,或者您可以拥有一个ExceptionHandler ,它将负责处理此类异常。

Both ways of handling this, should be OK and you may choose between each depending on your scenario. 处理此问题的两种方法都应该没问题,您可以根据您的方案在每种方法之间进行选择。

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: 如果您使用Spring Data,并且如果您的方法在Hibernate找不到匹配项时返回null ,请确保将@org.springframework.lang.Nullable添加到您的方法签名:

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: 这是因为Spring Data会检查方法的可空性,如果缺少注释,它将强制您始终需要返回一个对象:

/* 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 . 我使用了Spring Boot 2.1.1.RELEASE和Spring Data 2.1.4.RELEASE

i am not complete sure, but i think the problem it is because of the type of return long, maybe you should use the Long wrapper, long does not allow null because it is a primitive, try to change to 我不完全确定,但我认为问题是因为返回的类型很长,也许你应该使用Long包装器,long不允许null因为它是原始的,试着改为

@Query("SELECT AVG(u.age) FROM #{#entityName} u")
Long averageAge();

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM