web-dev-qa-db-fra.com

Comment renvoyer un seul résultat de Spring-Data-JPA?

J'essaie d'obtenir un seul résultat à partir d'une requête Spring Data. Je cherche à renvoyer le plus grand ID d'une table utilisateur. J'espérais que ce serait simple, mais je suis un peu perdu.

Jusqu'à présent, sur la base de ceci SO post , je suis arrivé à la conclusion que je dois utiliser un Specification pour définir ma requête et Paged résultats, spécifiant le nombre de résultats que je veux récupérer. Malheureusement, je reçois une exception d'accès aux données HibernateJdbcException.

Mon Specification/Predicate est censé être assez simple et refléter: from User order by id:

Page<User> result =userRepository.findAll(new Specification<User>() {
    @Override
    public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
        query.orderBy(cb.desc(root.get("id")));
        return query.getRestriction();
    }
}, new PageRequest(0, 10));

MatcherAssert.assertThat(result.isFirstPage(), is(true));
User u = result.getContent().get(0);

Exception levée:

org.springframework.orm.hibernate3.HibernateJdbcException: JDBC exception on Hibernate data access: SQLException for SQL [n/a]; SQL state [90016]; error code [90016]; could not extract ResultSet; nested exception is org.hibernate.exception.GenericJDBCException: could not extract ResultSet
    at org.springframework.orm.hibernate3.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.Java:651)
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.Java:106)
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.Java:403)
    at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.Java:58)
    at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.Java:213)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.Java:163)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.Java:172)
    at org.springframework.data.jpa.repository.support.LockModeRepositoryPostProcessor$LockModePopulatingMethodIntercceptor.invoke(LockModeRepositoryPostProcessor.Java:92)
    ...
    ...
Caused by: org.hibernate.exception.GenericJDBCException: could not extract ResultSet
    at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.Java:54)
    at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.Java:126)
    at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.Java:112)
    at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.extract(ResultSetReturnImpl.Java:89)
    at org.hibernate.loader.Loader.getResultSet(Loader.Java:2065)
    at org.hibernate.loader.Loader.executeQueryStatement(Loader.Java:1862)
    at org.hibernate.loader.Loader.executeQueryStatement(Loader.Java:1838)
    at org.hibernate.loader.Loader.doQuery(Loader.Java:909)
    at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.Java:354)
    at org.hibernate.loader.Loader.doList(Loader.Java:2553)
    at org.hibernate.loader.Loader.doList(Loader.Java:2539)
    at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.Java:2369)
    at org.hibernate.loader.Loader.list(Loader.Java:2364)
    at org.hibernate.loader.hql.QueryLoader.list(QueryLoader.Java:496)
    at org.hibernate.hql.internal.ast.QueryTranslatorImpl.list(QueryTranslatorImpl.Java:387)
    at org.hibernate.engine.query.spi.HQLQueryPlan.performList(HQLQueryPlan.Java:231)
    at org.hibernate.internal.SessionImpl.list(SessionImpl.Java:1264)
    at org.hibernate.internal.QueryImpl.list(QueryImpl.Java:103)
    at org.hibernate.jpa.internal.QueryImpl.list(QueryImpl.Java:573)
    at org.hibernate.jpa.internal.QueryImpl.getResultList(QueryImpl.Java:449)
    at org.hibernate.jpa.criteria.compile.CriteriaQueryTypeQueryAdapter.getResultList(CriteriaQueryTypeQueryAdapter.Java:67)
    at org.springframework.data.jpa.repository.query.QueryUtils.executeCountQuery(QueryUtils.Java:406)
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository.readPage(SimpleJpaRepository.Java:433)
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll(SimpleJpaRepository.Java:332)
    at Sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at Sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.Java:57)
    at Sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.Java:43)
    at Java.lang.reflect.Method.invoke(Method.Java:606)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.executeMethodOn(RepositoryFactorySupport.Java:358)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.Java:343)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.Java:172)
    at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.Java:96)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.Java:260)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.Java:94)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.Java:172)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.Java:155)
    ... 46 more
Caused by: org.h2.jdbc.JdbcSQLException: Column "USER1_.ID" must be in the GROUP BY list; SQL statement:
/* select count(generatedAlias0) from User as generatedAlias0, User as generatedAlias1 where 1=1 order by generatedAlias1.id desc */ select count(user0_.id) as col_0_0_ from user user0_ cross join user user1_ where 1=1 order by user1_.id desc [90016-173]
    at org.h2.message.DbException.getJdbcSQLException(DbException.Java:331)
    at org.h2.message.DbException.get(DbException.Java:171)
    at org.h2.message.DbException.get(DbException.Java:148)
    at org.h2.expression.ExpressionColumn.updateAggregate(ExpressionColumn.Java:166)
    at org.h2.command.dml.Select.queryGroup(Select.Java:344)
    at org.h2.command.dml.Select.queryWithoutCache(Select.Java:620)
    at org.h2.command.dml.Query.query(Query.Java:314)
    at org.h2.command.dml.Query.query(Query.Java:284)
    at org.h2.command.dml.Query.query(Query.Java:36)
    at org.h2.command.CommandContainer.query(CommandContainer.Java:91)
    at org.h2.command.Command.executeQuery(Command.Java:195)
    at org.h2.jdbc.JdbcPreparedStatement.executeQuery(JdbcPreparedStatement.Java:106)
    at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.extract(ResultSetReturnImpl.Java:80)
    ... 78 more

Je suis un peu perdu par l'erreur Hibernate - il demande une clause group. Je suppose que cela a quelque chose à voir avec la façon dont j'ai créé le prédicat, mais je ne sais pas comment créer un prédicat simple comme celui-ci.

[~ # ~] modifier [~ # ~]

Comme suggéré par @OliverGierke, j'ai essayé de supprimer la root = query.from(User.class) mais la mise en veille prolongée génère toujours la même erreur (j'ai activé le suivi complet des requêtes de mise en veille prolongée). Étrangement, cependant, cette fois, il n'y a pas de GROUP BY Dans le SQL généré, donc je suis encore plus confus qu'auparavant.

2014-03-18 11:59:44,475 [main] DEBUG org.hibernate.SQL - 
    /* select
        count(generatedAlias0) 
    from
        User as generatedAlias0 
    order by
        generatedAlias0.id desc */ select
            count(user0_.id) as col_0_0_ 
        from
            user user0_ 
        order by
            user0_.id desc
Hibernate: 
    /* select
        count(generatedAlias0) 
    from
        User as generatedAlias0 
    order by
        generatedAlias0.id desc */ select
            count(user0_.id) as col_0_0_ 
        from
            user user0_ 
        order by
            user0_.id desc
2014-03-18 11:59:44,513 [main] WARN  hibernate.engine.jdbc.spi.SqlExceptionHelper - SQL Error: 90016, SQLState: 90016
2014-03-18 11:59:44,513 [main] ERROR hibernate.engine.jdbc.spi.SqlExceptionHelper - Column "USER0_.ID" must be in the GROUP BY list; SQL statement:
/* select count(generatedAlias0) from User as generatedAlias0 order by generatedAlias0.id desc */ select count(user0_.id) as col_0_0_ from user user0_ order by user0_.id desc [90016-173]
14
Eric B.

Vous n'utilisez pas la fonction root qui est transférée dans l'instance Specification pour invoquer la méthode .get(…). Cela ne permet pas à l'instance d'enregistrer id en cours d'utilisation et donc de l'ajouter de manière transparente au jeu de résultats.

La simple suppression de root = query.from(User.class); devrait suffire.

Ce qui m'intrigue cependant, c'est que vous mentionnez que vous avez l'intention de créer une requête "find by id". Ce que vous êtes en train de construire est une "recherche de tous classés par identifiant". Si c'est vraiment le premier que vous souhaitez obtenir, il existe une méthode findOne(…) prédéfinie sur CrudRepository que vous pouvez utiliser.

Compte tenu des commentaires ci-dessous, il semble que ce que vous essayez réellement de réaliser soit de trouver un seul utilisateur avec le plus petit identifiant. Cela peut également être réalisé en étendant simplement PagingAndSortingRepository puis en utilisant le code client comme ceci:

interface UserRepository extends PagingAndSortingRepository<User, Long> { … }

Page<User> users = repository.findAll(new PageRequest(0, 1, Direction.ASC, "id"));
User user = users.getContent.get(0);

Cela limitera le résultat à la première page par une taille de page de 1 avec un ordre croissant par id.

12
Oliver Drotbohm

Je ne sais pas pourquoi vous récupérez une collection pour obtenir un seul résultat. Corrigez-moi si je me trompe, mais la solution à votre problème, telle que je l'ai interprétée, est très facile à résoudre en utilisant @Query. Ajoutez ce qui suit à votre interface de référentiel.

@Query("SELECT max(t.id) FROM #{#entityName} t")
Integer getMaxId();
12
Bart