JDBIでDynamic Query（動的クエリ）


```groovy
@Grab('org.jdbi:jdbi:2.71')
@Grab('org.antlr:stringtemplate:3.2.1')
@Grab('com.h2database:h2:1.4.191')

import org.skife.jdbi.v2.DBI
import org.skife.jdbi.v2.sqlobject.SqlQuery
import org.skife.jdbi.v2.sqlobject.customizers.Define
import org.skife.jdbi.v2.sqlobject.stringtemplate.UseStringTemplate3StatementLocator

@UseStringTemplate3StatementLocator
public interface MyDAO {
    @SqlQuery("<query> --comment\\<>")
    String query(@Define("query") String query)
}

DBI dbi = new DBI("jdbc:h2:mem:test")
MyDAO myDAO = dbi.onDemand(MyDAO.class)
String result = myDAO.query("select now()")

println result
```

## まとめ

1. JDBIが依存しているバージョンの `org.antlr:stringtemplate` を依存性に追加
1. DAOに `@UseStringTemplate3StatementLocator` を付ける
1. SQL自体をBindしたいところに `<query>` のように書く
1. `@Define` をBindする変数に付ける

## よもやま

JDBIの`org.antlr:stringtemplate`のdependencyが、optionalがtrueになっているため明示的に依存性として付ける必要がある。これをしないとアノテーションエラーが出る。JDBIが依存しているバージョンを指定せず、最新を指定してもエラーが出たので正直ハマった。

```java
java.lang.annotation.AnnotationFormatError: Invalid default: public abstract java.lang.Class org.skife.jdbi.v2.sqlobject.stringtemplate.UseStringTemplate3StatementLocator.errorListener()
```

`@UseStringTemplate3StatementLocator`を付ける場合、Bindする以外の`<`は`\\<`のようにエスケープする必要がある。例えば `select * from where now() < foo_at`のようなSQL。
これをしないとアプリ自体は止まらないが、例外が発生する。

```java
problem parsing template 'PHF1ZXJ5PiAtLWNvbW1lbnRcPD4gPA=='
line 1:22: unexpected char: '<'
	at org.antlr.stringtemplate.language.AngleBracketTemplateLexer.nextToken(AngleBracketTemplateLexer.java:149)
	at antlr.TokenBuffer.fill(TokenBuffer.java:69)
	at antlr.TokenBuffer.LA(TokenBuffer.java:80)
	at antlr.LLkParser.LA(LLkParser.java:52)
	at org.antlr.stringtemplate.language.TemplateParser.template(TemplateParser.java:103)
	at org.antlr.stringtemplate.StringTemplate.breakTemplateIntoChunks(StringTemplate.java:850)
	at org.antlr.stringtemplate.StringTemplate.setTemplate(StringTemplate.java:441)
	at org.antlr.stringtemplate.StringTemplateGroup.defineTemplate(StringTemplateGroup.java:679)
	at org.skife.jdbi.v2.sqlobject.stringtemplate.StringTemplate3StatementLocator.locate(StringTemplate3StatementLocator.java:260)
	at org.skife.jdbi.v2.SQLStatement.wrapLookup(SQLStatement.java:1284)
	at org.skife.jdbi.v2.SQLStatement.internalExecute(SQLStatement.java:1293)
	at org.skife.jdbi.v2.Query.fold(Query.java:173)
	at org.skife.jdbi.v2.Query.first(Query.java:273)
	at org.skife.jdbi.v2.Query.first(Query.java:264)
	at org.skife.jdbi.v2.sqlobject.ResultReturnThing$SingleValueResultReturnThing.result(ResultReturnThing.java:110)
	at org.skife.jdbi.v2.sqlobject.ResultReturnThing.map(ResultReturnThing.java:46)
	at org.skife.jdbi.v2.sqlobject.QueryHandler.invoke(QueryHandler.java:43)
	at org.skife.jdbi.v2.sqlobject.SqlObject.invoke(SqlObject.java:212)
	at org.skife.jdbi.v2.sqlobject.SqlObject$2.intercept(SqlObject.java:109)
	at org.skife.jdbi.v2.sqlobject.CloseInternalDoNotUseThisClass$$EnhancerByCGLIB$$6a1f7715.query(<generated>)
	at MyDAO$query.call(Unknown Source)
	at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:45)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:110)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:122)
	at jdbi.run(jdbi.groovy:21)
	at groovy.lang.GroovyShell.runScriptOrMainOrTestOrRunnable(GroovyShell.java:261)
	at groovy.lang.GroovyShell.run(GroovyShell.java:522)
	at groovy.lang.GroovyShell.run(GroovyShell.java:511)
	at groovy.ui.GroovyMain.processOnce(GroovyMain.java:650)
	at groovy.ui.GroovyMain.run(GroovyMain.java:381)
	at groovy.ui.GroovyMain.process(GroovyMain.java:367)
	at groovy.ui.GroovyMain.processArgs(GroovyMain.java:126)
	at groovy.ui.GroovyMain.main(GroovyMain.java:106)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:497)
	at org.codehaus.groovy.tools.GroovyStarter.rootLoader(GroovyStarter.java:106)
	at org.codehaus.groovy.tools.GroovyStarter.main(GroovyStarter.java:128)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:497)
	at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)
```

既に存在するDAOに上記アノテーションを付ける場合、`<`が存在するか調べエスケープすることを徹底する。

### 参考

http://stackoverflow.com/questions/19424573/how-to-do-in-query-in-jdbi
http://search.maven.org/#artifactdetails%7Corg.jdbi%7Cjdbi%7C2.71%7Cjar