Агрегатные функции Spring Data JPA для пустого набора результатов - PullRequest
0 голосов
/ 16 сентября 2018

Я работаю над проектом, включающим Spring и JPA/Hibernate. Драйвер базы данных, используемый в моей среде разработки, H2. В моем приложении есть страница, на которой отображается статистика, одним из примеров такой статистики является средний возраст моих пользователей. Однако, когда я пытаюсь получить средний возраст, используя JPQL, я получаю исключение

Result must not be null!

Для простоты предположим, что я храню возраст как integer для каждого User объекта (в моем приложении это, конечно, не так, но это не важно для моей проблемы).

Модель пользователя

@Entity
public class User implements Identifiable<Long> {
    private int age;
    // more fields and methods, irrelevant
}

Репозиторий пользователя

@Repository
public interface UserRepository extends CrudRepository<User, Long> {
    @Query("SELECT AVG(u.age) FROM #{#entityName} u")
    long averageAge();
}

Не могу понять, почему вызов UserRepository#averageAge(); вызывает исключение. Я попытался заменить функцию AVG в запросе на COUNT, и это ведет себя как ожидалось. Я также пытался использовать SQL-запрос и установку nativeQuery = true в аннотации, но пока безрезультатно. Конечно же, я могу решить эту проблему, выбрав всех пользователей и рассчитав средний возраст в простой 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

решаемые

Исключение было вызвано тем, что AVG() возвращает null при выполнении на пустой таблице. Я исправил это, изменив запрос (вдохновленный ответом на этот вопрос ) следующим образом:

@Query("SELECT coalesce(AVG(u.age), 0) FROM #{#entityName} u")
long averageAge();

Ответы [ 3 ]

0 голосов
/ 16 сентября 2018

Я не совсем уверен, но я думаю, что проблема заключается в типе возврата long, может быть, вам следует использовать обертку Long, long не допускает null, потому что это примитив, попробуйте изменить на

@Query("SELECT AVG(u.age) FROM #{#entityName} u")
Long averageAge();
0 голосов
/ 07 февраля 2019

Если вы используете Spring Data и ваш метод возвращает null, когда Hibernate не может найти соответствие, убедитесь, что вы добавили @org.springframework.lang.Nullable к сигнатуре вашего метода:

public interface SomeRepositoryCustom {
    @org.springframework.lang.Nullable
    public Thing findOneThingByAttr(Attribute attr) {
        /* ...your logic here... */
    }
}

Это потому, что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... */

Я использовал версию Spring Boot 2.1.1.RELEASE и Spring Data 2.1.4.RELEASE.

0 голосов
/ 16 сентября 2018

Кажется, что исключение EmptyResultDataAccessException генерируется, когда ожидалось, что в результате запроса будет хотя бы одна строка (или элемент), но ни одна не была возвращена.

Соответствующую документацию по этому вопросу можно найти здесь .

Я бы предложил выполнить тот же запрос, который пытается выполнить эта попытка, для дальнейшей проверки этой теории. Теперь хороший вопрос, что с этим делать.

У вас есть два варианта. Либо поймайте исключение EmptyResultDataAccessException в точке вызова и обработайте его непосредственно там, либо вы можете получить ExceptionHandler, которому будет поручено обрабатывать такие исключения.

Оба способа справиться с этим должны быть в порядке, и вы можете выбрать один из них в зависимости от вашего сценария.

...