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") } }