= anormで複数DBへのアクセス =

== 自前のjava.sql.Connection ==

play frameworkは、基本的にconf/application.confに記述されたデータベースに対して自動的なアクセスを提供するようだが、これでは「あらかじめ決められたただ一つのDB」へのアクセスしかできない。
playの将来バージョンでは、この記述が複数認められるようになる模様であるが、しかし実行時にDBを自由に切り替えるというわけにはいかないようだ。

しかし、Anorm.scalaのソースを読んでみるとわかるのだが、実はアプリ側で任意のjava.sql.Connectionを用意してやりさえすれば、そこにアクセスすることは簡単にできる。例えば、
{{{
val clients = SQL("select * from clients order by id")().map(row => .....
}}}
と記述すると、デフォルトのデータベースに対するアクセスになるが、これに自前のjava.sql.Connectionを指定することにより、
{{{
val clients = SQL("select * from clients order by id")(connection).map(row => .....
}}}
そのデータベースへのアクセスとなる。。。。

。。。はずだが、実際にはならない。これはanormのコードのバグのため(play-scala 0.9.1時点)。
play.db.anorm.Sqlトレイトのソースは以下のようになっているが、

{{{
    trait Sql {

        import SqlParser._
        import scala.util.control.Exception._

        def connection = play.db.DB.getConnection

        def getFilledStatement(connection:java.sql.Connection, getGeneratedKeys:Boolean=false):java.sql.PreparedStatement

        def filledStatement = getFilledStatement(connection)

        def apply(conn:java.sql.Connection=connection) = Sql.resultSetToStream(resultSet(connection))

        def resultSet (conn:java.sql.Connection=connection) = (getFilledStatement(connection).executeQuery())
}}}

例えば、
{{{
 def apply(conn:java.sql.Connection=connection) = Sql.resultSetToStream(resultSet(connection))
}}}
は
{{{
 def apply(conn:java.sql.Connection=connection) = Sql.resultSetToStream(resultSet(conn))
}}}
でなければいけない。2.0-previewでは修正されているのだろうか???





=== デフォルトデータベースの設定は必要 ===

ただし、現在のplay-scala 0.9.1では、自前のjava.sql.Connectionを使う場合にも「デフォルトデータベース」の設定は必要になる。
設定せずに、上に述べたアクセスを使用とすると、以下のエラーが発生する。

{{{
Exception in thread "main" java.lang.NullPointerException
	at play.exceptions.PlayException.getInterestingStrackTraceElement(PlayException.java:56)
	at play.exceptions.DatabaseException.<init>(DatabaseException.java:24)
	at play.db.DB.getConnection(DB.java:63)
	at play.db.anorm.Sql$class.connection(Anorm.scala:899)
	at play.db.anorm.SqlQuery.connection(Anorm.scala:938)
	at play.db.anorm.Sql$class.apply(Anorm.scala:905)
	at play.db.anorm.SqlQuery.apply(Anorm.scala:938)
	at Main$.main(Main.scala:30)
	at Main.main(Main.scala)
}}}

この理由はソースを見ればわかる。先のSQL()メソッド呼び出しはplay.db.anorm.Sqlというトレイトを返すのだが、このトレイトの定義は以下のようになっている。

{{{
    trait Sql {
....
        def connection = play.db.DB.getConnection
....
        def apply(conn:java.sql.Connection=connection) = Sql.resultSetToStream(resultSet(connection))
}}}
つまり、Sqlトレイトが生成されるときには、必ずplay.db.DB.getConnection(これはJava側で定義)が呼び出されてConnectionが取得されるが、applyの引数が与えられない場合にはこのConnectionが使用される。デフォルトデータベースが指定されていないと、play.db.DB.getConnectionはエラー発生してしまう。

つまり、anormだけを取り出してplayとは無関係に使うような前提にはなっていない。あくまでも、playのアプリケーションは単一のデータベースを前提としており、自由なConnection指定は副次的なものとの位置づけとなる。

== 自前のjava.sql.Connectionの管理 ==

ただし、自前のjava.sql.Connectionを扱うためには、次の二点をクリアしなければならない。

 * 一つのリクエスト・レスポンスサイクルの中で、一つのConnectionを使い回したいと思われるので、それをキャッシュする仕組みが必要。
 * レスポンスが終了したらそれをクローズするか、あるいは再利用のための準備をする必要がある。

前者は何とでもなる。後者については、@Finallyアノテーションを付けたメソッドのなかでThreadLocalに保存されたConnectionを廃棄(するなり初期化するなり)すればよい。
{{{
  @Finally
  def finallyProc = {
    Logger.info("clean-up")
  }
}
}}}