Spring Boot 1.5系のバッチ的な処理に急ぎ手を入れる必要があって、RepositoryにNativeQueryでページングするようなメソッド追加したら謎のエラーでめっちゃハマったお話です。*1
Spring Bootバージョン
1.5系の古いやつですね。過去の遺産じゃないかぎり使うことはないですね…
id 'org.springframework.boot' version '1.5.22.RELEASE'
Repositoryの実装
Repositoryでは、NativeQueryを使ってページングさせるようなメソッドを定義しています。
@Query( nativeQuery = true, value = "select * from users ", countQuery = "select count(*) from users" ) Page<UsersEntity> find(Pageable pageable);
動作検証用のコード
動作検証用にこんな感じのテストコードを書いています。
@Test public void test() throws Exception { final Page<UsersEntity> result = sut.find(new PageRequest(0, 10)); System.out.println("result = " + result); }
実行結果
実行すると、InvalidJpaQueryMethodException
が発生してしまいます。
Caused by: org.springframework.data.jpa.repository.query.InvalidJpaQueryMethodException: Cannot use native queries with dynamic sorting and/or pagination in method public abstract org.springframework.data.domain.Page siosio.datajpaexample.repository.UsersRepository.find(org.springframework.data.domain.Pageable) at org.springframework.data.jpa.repository.query.NativeJpaQuery.<init>(NativeJpaQuery.java:58)
例外が発生している、NativeJpaQuery
の実装(↓)を見てみるとPageable
パラメータを持つ場合には#pageable
という文字列がSQL内にないとダメなようです。
public NativeJpaQuery(JpaQueryMethod method, EntityManager em, String queryString, EvaluationContextProvider evaluationContextProvider, SpelExpressionParser parser) { super(method, em, queryString, evaluationContextProvider, parser); Parameters<?, ?> parameters = method.getParameters(); boolean hasPagingOrSortingParameter = parameters.hasPageableParameter() || parameters.hasSortParameter(); boolean containsPageableOrSortInQueryExpression = queryString.contains("#pageable") || queryString.contains("#sort"); if (hasPagingOrSortingParameter && !containsPageableOrSortInQueryExpression) { throw new InvalidJpaQueryMethodException( "Cannot use native queries with dynamic sorting and/or pagination in method " + method); } }
SQLに#pageable
を追加して再実行してみよう
Repositoryの実装を修正して、SQLに#pageable
を追加してみます。
@Query( nativeQuery = true, value = "select * from users #pageable", countQuery = "select count(*) from users" ) Page<UsersEntity> find(Pageable pageable);
対応後でも残念ながら別のエラーで落ちてしまいましたね…SQLを実行するところまではいっているようなのでどんなSQLを実行しようとしているのか見てみましょう。
could not execute query; nested exception is org.hibernate.exception.GenericJDBCException: could not execute query org.springframework.orm.jpa.JpaSystemException: could not execute query; nested exception is org.hibernate.exception.GenericJDBCException: could not execute query at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:333) at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:244)
まさかのSQL内の#pageable
が残ったままになってますね(´・ω・`)
エラーの回避方法
Spring Data JPA @Query | Baeldungの4.3. Spring Data JPA Versions Prior to 2.0.4
に回避方法が書いてありますね。
どうやら、Spring Data JPA 2.0.4より前の場合には、バグ?的なものがあるようです。
リンク先の回避方法を真似て、#pageable
をSQLコメントとして書いてあげます。大事なのは、#pageable
の前後に改行を入れることですね。
改行忘れると、SQLの最後にくっつくlimit
までコメントになってしまいます。
@Query( nativeQuery = true, value = "select * from users \n -- #pageable \n", countQuery = "select count(*) from users" ) Page<UsersEntity> find(Pageable pageable);
これで正常に実行できるようになりました。
result = Page 2 of 1 containing UNKNOWN instances
Spring Data JPAを2.0.4以降にしてみると
最初に#pageable
の実装がないよと例外投げてたNativeJpaQuery
から#pageable
に関する実装が消えていますね。
public NativeJpaQuery(JpaQueryMethod method, EntityManager em, String queryString, EvaluationContextProvider evaluationContextProvider, SpelExpressionParser parser) { super(method, em, queryString, evaluationContextProvider, parser); Parameters<?, ?> parameters = method.getParameters(); if (parameters.hasSortParameter() && !queryString.contains("#sort")) { throw new InvalidJpaQueryMethodException( "Cannot use native queries with dynamic sorting in method " + method); } this.resultType = getTypeToQueryFor(); }
ということで、Repositoryの実装はこんな感じで#pageable
なしで書けるようになります。
@Query( nativeQuery = true, value = "select * from users", countQuery = "select count(*) from users" ) Page<UsersEntity> find(Pageable pageable);
#pageable
は消えたけど、#sort
に関する実装がまだ残っているのでどうなるのか見てみます。
Repositoryの実装をソートのみに変えてみます。SQLには、#sort
を含めてあげます。
@Query( nativeQuery = true, value = "select * from users #sort" ) List<UsersEntity> find(Sort sort);
エラーにならず実行できました。#sort
に関する処理は正しく動くようです。
result = [siosio.datajpaexample.entity.UsersEntity@6dff619a, siosio.datajpaexample.entity.UsersEntity@3f73d455, siosio.datajpaexample.entity.UsersEntity@24df2d20]
まとめ
バージョンアップして幸せになりたい。
おわり。
*1:色々と制約があってこうするしかなかった…