Pagination

次のようなドメインクラスを作ります。

class DomainA {
    static constraints = {
    	text(blank:false)
    }
    String text
}

このクラスは、text という属性を持っているだけの Domain クラスです。


次に、View と Controller を作ります。
これは、実験用のアプリですので、scaffolding した方が楽チンです。
次のように入力します。

grails generate-all DmainA

この状態でアプリケーションを起動して、DomainA クラスのオブジェクトを 11個作ります。
View でみると次のようになります。

ブラウザのアドレス欄の URL は、次のようになっています。

http://localhost:8080/pagination/domainA/list

この状態で、画面の左下にある[次へ]ボタンを押してページ送りします。
すると、オブジェクトが一つだけリスト表示され、ブラウザのアドレス欄の URL は次のようになります。

http://localhost:8080/pagination/domainA/list?offset=10&max=10&sort=id&order=asc

ここまで正常です。

ここで、ブラウザのアドレス欄の URL を次のように変えます。

http://localhost:8080/pagination/domainA/list?offset=A&max=10&sort=id&order=asc

offset のところを `10' から `A' に変えています。

おもむろに、変更した URL に移動すると次の例外が発生します。

org.codehaus.groovy.runtime.InvokerInvocationException: org.springframework.beans.TypeMismatchException: Failed to convert value of type [java.lang.String] to required type [java.lang.Integer]; nested exception is java.lang.NumberFormatException: For input string: "A"
	at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:92)
	at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:226)
	at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:899)
	at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:946)
	at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:740)
	at groovy.lang.Closure.call(Closure.java:292)
	at groovy.lang.Closure.call(Closure.java:287)
(省略)
Caused by: org.springframework.beans.TypeMismatchException: Failed to convert value of type [java.lang.String] to required type [java.lang.Integer]; nested exception is java.lang.NumberFormatException: For input string: "A"
	at DomainAController$_closure2.doCall(DomainAController.groovy:11)
	at DomainAController$_closure2.doCall(DomainAController.groovy)
Caused by: java.lang.NumberFormatException: For input string: "A"
	at java.lang.NumberFormatException.forInputString(NumberFormatException.java:48)
	at java.lang.Integer.parseInt(Integer.java:447)
	at java.lang.Integer.valueOf(Integer.java:526)
	at java.lang.Integer.decode(Integer.java:919)
	... 2 more

Integer.parseInt に失敗して例外が発生したのですが、これは パラメタ `max' でも同様に同じ例外が発生します。

この例外が発生する Controller の該当場所は、次のようになっています。

def list = {
   if(!params.max) params.max = 10
   [ domainAList: DomainA.list( params ) ]
}

このコードは、Grails の `grails generate-all' で生成したものです。問題の箇所は params を与えている DomainA.list() です。
このメソッドは、起動に HibernateGrailsPlugin クラスによって自動的に付与されるダイナミックメソッドです。

次のようにダイナミックメソッドを登録しています。

def listMethod = new ListPersistentMethod(sessionFactory, classLoader)
metaClass.'static'.list = {-> listMethod.invoke(domainClassType, "list", [] as Object[])}
metaClass.'static'.list = {Map args -> listMethod.invoke(domainClassType, "list", [args] as Object[])}

ListPersistentMethod クラスを見てみると、最終的には GrailsHibernateUtilクラスの populateArgumentsForCriteria() で例外が発生していることが分かります。

コードを見ると、Integer.parseInt() で int 型に変換できることをあてにしているコードになっています。
例えば、params に「変換で失敗する場合はこの値にしてください」というものを与えることが出来たら安心して利用できるわけですが、今はそうはなっていません。pagination が可能なメソッド全てがそうです。

要するに、pagination に関係するクエリパラメタの書き換えにより、例外画面になっては困る場合、pagination 可能なメソッドを呼び出す前に、変換に失敗しないような考慮を呼び出し側で行う必要があるということでした…。(Grails 1.0.2での話)