= 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.(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") } } }}}