Slick 2.0.0 documentation - 04 Connection/Transactions

Permalink to Connections/Transactions - Slick 2.0.0 documentation

Connections / Transactions 

クエリはプログラムのどこにでも書くことが出来る。クエリを実行する際には、データベースコネクションが必要になる。

Database connection 

用いるJDBCデータベースに対してどのように接続するのかを、それらの情報をカプセル化したDatabaseオブジェクトを作成することで、Slickへ伝える事が出来る。Databaseオブジェクトを作成するにはscala.slick.jdbc.JdbcBackend.Databaseにいくつかのファクトリメソッドが用意されており、どのような方法で接続するかによって使い分ける事が出来る。

Using a JDBC URL 

JDBC URLを用いて接続を行う際には、`forURL`を用いる事が出来る。(正しいURLを記述する際には、データベースのJDBCドライバー用ドキュメントを参照して欲しい)

val db = Database.forURL("jdbc:h2:mem:test1;DB_CLOSE_DELAY=-1", driver="org.h2.Driver")

ここでは例として、新しく空のデータベースへと接続をしてみる。用いるのはインメモリ型のH2データベースであり、データベース名がtest1、そしてJVMが終了するまで残り続けるような(DB_CLOSE_DELAY=-1はH2データベース特有のオプション)データベースとなっている。

Using a DataSource 

DataSourceオブジェクトを既に持っているのなら、`forDataSource`を用いてDatabaseオブジェクトを作成出来る。もしアプリケーションフレームワークのコネクションプールからDataSourceオブジェクトを取得出来るのなら、Slickのプールへと繋いで欲しい。

val db = Database.forDataSource(dataSource: javax.sql.DataSource)

後でセッションを作成する時には、コネクションはプールから取得出来るし、セッションが閉じた時に、コネクションはプールへ返却される。

Using a JNDI Name 

もしJNDIを用いているのなら、DataSourceオブジェクトが見つかるJNDIの名前をforNameに渡してあげたら良い。

Session handling 

Databaseオブジェクトを持っているのなら、SessionオブジェクトにSlickがカプセル化したデータベースコネクションを開く事が出来る。

Automatically closing Session scope 

DatabaseオブジェクトのwithSession)関数は、関数を引数に、実行後に接続の閉じるSessionを作る。もしコネクションプールを用いたのならば、Sessionを閉じるとコネクションはプールへと返却される。

val query = for (c <- coffees) yield c.name
val result = db.withSession {
  session =>
    query.list()( session )
}

withSessionのスコープの外側で定義されたクエリが使われている事を上の例から確認出来る。データベースに対してクエリを実行する関数はSessionを必要とする。先ほどの例ではlist関数を用いてクエリを実行し、Listとして結果を取得している。(クエリを実行する関数は暗黙的な変換を通して作られる)

ただし、デフォルトの設定ではデータベースセッションはauto-commitモードになっている。insertinsertAllのようなデータベースへの呼び出しは原子的(必ず成功するか失敗するかのいずれかが保証されている)に実行される。いくつかの状態を包括するにはTransactionsを用いる。

注意: もしSessionオブジェクトがwithSessionのスコープ以外で用いられていたのなら、その接続は既に閉じられており、妥当な利用法にはなっていない。利用を避けるべきではあるば、このような状態を避ける方法がいくつかあり、例としてクロージャを用いる(withSessionスコープ内にてFutureを用いるなど)、varへセッションを割り当てる、withSessionスコープの返り値としてセッションを返却するといった方法がある。

Implicit Session 

Sessionを暗黙的なものとしてマークすると、データベースに対する呼び出しを行う関数に対して明示的にSessionを渡す必要がなくなる。

val query = for (c <- coffees) yield c.name
val result = db.withSession {
  implicit session =>
    query.list // <- takes session implicitly
}
// query.list // <- would not compile, no implicit value of type Session

これはオプショナルな使い方ではあるが、用いるとよりコードを綺麗にする事が出来る。

Transactions 

SessionオブジェクトのwithTransaction関数をトランザクションを作成するために使う事が出来る。そのブロックにおいて、1つのトランザクション処理が実行されることになる。もし例外が発生したのなら、Slickはトランザクションをブロックの終了箇所までロールバックさせる。ブロック内のどこからでもrollback関数を呼び出すことでブロックの末尾までロールバックを強制して起こさせる事も出来る。注意して欲しいのは、Slickはデータベースのオペレーションとしてのロールバックを行うのであり、他のScalaコードの影響を引き起こさない。

session.withTransaction {
  // your queries go here
  if (/* some failure */ false){
    session.rollback // signals Slick to rollback later
  }
} // <- rollback happens here, if an exception was thrown or session.rollback was called

もしSessionオブジェクトをまだ持っていないのなら、DatabaseオブジェクトのwithTransaciton関数を直接呼ぶ事が出来る。

db.withTransaction{
  implicit session =>
    // your queries go here
}

Manual Session handling 

この方法は推奨されない。もししなければならない場面があるのなら、Sessionを手動で取り扱うことも出来る。

val query = for (c <- coffees) yield c.name
val session : Session = db.createSession
val result  = query.list()( session )
session.close

Passing sessions around 

Slickのクエリに対し、再利用可能な関数を書くことが出来る。これらの関数はSessionを必要としないものであり、クエリのフラグメントやアセンブリ化されたクエリを生成する。もしこれらの関数内でクエリを実行したいのなら、Sessionが必要になる。その際は、関数のシグネチャにおいて(出来れば暗黙的なものとして)引数にあたえてあげるか、もしくはいくつかの同様の関数を包括して、共通化したコードを削除するためにセッションを保持したクラスにする。

class Helpers(implicit session: Session){
  def execute[T](query: Query[T,_]) = query.list
  // ... place further helpers methods here
}
val query = for (c <- coffees) yield c.name
db.withSession {
  implicit session =>
  val helpers = (new Helpers)
  import helpers._
  execute(query)
}
// (new Helpers).execute(query) // <- Would not compile here (no implicit session)

Dynamically scoped sessions 

セッションは長い間開きっぱなしにはしたくないが、必要な時にはすぐに開いたり閉じたりしたいと考えるだろう。上記の例では、クエリを実行するために必要な時に暗黙的なセッション引数を用いてセッションスコープトランザクションスコープを使っていた。

別の方法として、共通化したコードの部分的なものを保存する、ということが、ファイルの先頭に追加に次の行を追加する事で行える。これにより、セッション引数無しのセッションスコープやトランザクションスコープを利用する事が出来る。

import Database.dynamicSession // <- implicit def dynamicSession : Session

現在のコールスタック内のどこかでwithDynSessionwithDynTransactionスコープが開かれていた場合において、dynamicSessionは適切なSessionを返却する暗黙的な関数となる。

db.withDynSession {
  // your queries go here
}

注意して欲しいのは、もしdynamicSessionがインポートさあれ、withDynSessionwithDynTransactionスコープの外側でクエリが実行されようとしているのならば、実行時例外を吐いてしまう事である。つまり、静的な安全性を犠牲にしているのである。dynamicSessionは内部的にDynamicVariableを用いる。これは動的にスコープのある変数を作成し、JavaのInheritableThreadLocalを順々に用いるものである。静的であることの安全性とスレッドの安全性に配慮して欲しい。

Connection Pools 

Slickは独自のコネクションプール実装を持っていない。JEEやSpringのようなある種のコンテナにおけるアプリケーションを動かす際、一般的にコンテナに提供されたコネクションプールを用いる事になるだろう。スタンドアローンなアプリケションにおいてはDBCPc3p0BoneCPのような外部のコネクションプールの実装を用いる事が出来る。

ちなみに、Slickはどこでも利用可能なプリペアドステートメントを持ってはいるが、独自でキャッシュをしたりはしない。よって、コネクションプールの設定において、プレペア度ステートメントのキャッシュを有効にすべきであるし、充分に大きなプールサイズを用意すべきだ。

Fork me on GitHub