Scala用DBアクセスライブラリ Querulous の使い方

(この記事は Scala Advent Calendar jp 2010 の1日目です。)


Scala用のScalaで書かれたDBアクセスのためのライブラリ、querulousについて書きます。

とりあえず読み方がよくわかりませんが、クゥエルァラス??みたいな感じです。すごく言いにくいので「クエララス」ってことにしましょう。英語の形容詞で、意味は「不平たらたらの、不平をブツブツ言う」らしいです。ヒドイ名前ですね。

querulousは以前紹介したKestrelと同様、Twitterで作られて使われているものです。もう辞めちゃったらしいですが、元 TwitterのNick Kallen氏が作ったそうです。今はGithub上でtwitterというIDの中で管理されています。

(12/8 追記 Nick Kallen氏はまだ辞めてませんでした。すみません、Alex Payne氏と勘違い。kzys さん指摘ありがとうございます)


twitter/querulous · GitHub


機能と特長をreadmeから抜粋すると

  • JDBCのめんどくさいことをいい感じにやっちゃうよ。
  • タイムアウトとか死活検知とかリトライとか、障害耐性も考えてるよ。
  • 統計情報とかいろんなデバッグログとか出すから便利だよ。
  • コードとか規約とか依存関係とか最小限で、SQLチューニングの邪魔はしないよ。
  • モジュール化されててカスタマイズもOKさ。

という感じらしいです。
「最小限」ってのは、例えばJNDIは使えなかったり、MySQLしかサポートしてなかったりです。
依存関係が最小限ってとこは納得しかねるんですが、Liftみたいなヘビーなものから比べれば少ないと思います。依存するのは以下のライブラリ群です。

  • configgy (設定とロギングのためのライブラリ)
  • xrayspecs (Specsの拡張ライブラリ、以下の3つと合わせてテスト用。この中の時間計測関連のクラス・関数がヘビーに使われてて、インターフェイスにも出てきます)
  • mysql-connector-java !! (潔くMySQLオンリーです)
  • commons-dbcp&commons-pool (ここも潔いですね)
  • objenesis (リフレクション用のライブラリ)
  • cglib&asm (バイトコードを実行時にゴニョゴニョするやつ)


これまたreadmeから、実装についての説明を抜粋すると

  • QuerulousはQueryEvaluators, Queries, Databasesの3つのコンポーネントがメイン。
    • QueryEvaluatorsはクエリ実行のためのインターフェイス
    • Queriesは SELECT/UPDATE/INSERT/DELETE クエリを抽象化したもの。直接Queriesを操作することはあんまりない。
    • DatabasesはDB接続の管理を行うもの。
  • この3つはそれぞれのFactoryがインターフェイスとして書かれてて、実装はdecorator-patternで重ねて使う。
val queryFactory = new DebuggingQueryFactory(new TimingOutQueryFactory(new SqlQueryFactory))
val query = queryFactory(...) // this query will have debugging information and timeouts!

(これはScalaなんだからtraitをwithで重ねればいいのに、と思うけど、そうはできないのかな・・・わからない)

使ってみる

使ってみましょう。querulousのjarは maven.twttr.com で公開されていて、sbtやmavenから参照することができるようになってます。
それぞれの設定は以下のとおりです。

sbtの設定

プロジェクトファイルに以下のようにリポジトリとライブラリへの参照を書けばOKです。以下はScala 2.8系の場合の設定です。2.7系の場合はquerulous_2.8.0のところをquerulous_2.7.7にしてください。なお、querulous_2.8.1はリポジトリに存在しないので、2.8.1で使う場合はquerulous_2.8.0を使いましょう。

import sbt._

class QuerulousSampleProject(info: ProjectInfo) extends DefaultProject(info) {
  val twitterRepos = "Twitter Maven Repository" at "http://maven.twttr.com"
  val querulous = "com.twitter" % "querulous_2.8.0" % "1.4.3"
}
Mavenの設定

Mavenの場合はpom.xmlに以下のとおりにrepositoryとdependencyの設定を書けばOKです。MavenとIvyの場合の説明は、readmeにも書いてありますが、readmeの説明はバージョン表記やartifactIdが古いものになっているので注意してください。以下の記述は上記のsbtの場合と同様Scala 2.8系向けです。2.7系の場合はquerulous_2.8.0のところをquerulous_2.7.7にしてください。

<repository>
  <id>twitter.com</id>
  <url>http://maven.twttr.com/</url>
</repository>

<dependency>
    <groupId>com.twitter</groupId>
    <artifactId>querulous_2.8.0</artifactId>
    <version>1.4.3</version>
</dependency>
QueryEvaluatorの取得

querulousでは、データベースにアクセスするために、まずQueryEvaluatorFactoryを作ります。前述の通り、decolator-patternになっていて、以下のようなコードで作成できます。(commons-dbcpを使ってコネクションプールを作る例です)

import com.twitter.xrayspecs.TimeConversions._
import com.twitter.querulous.evaluator._
import com.twitter.querulous.query._
import com.twitter.querulous.database._

object Database {
  val factory = new StandardQueryEvaluatorFactory(
      new ApachePoolingDatabaseFactory(
        minConns    = 2,
        maxConns    = 200,
        checkIdle   = 0 seconds,
        maxWait     = 200 millis,
        checkHealth = false,
        evictTime   = 30 minutes
      ), new SqlQueryFactory)
  // secondsとかmillisはcom.twitter.xrayspecs.TimeConversionsで定義されています
}

続いて、ここで作成したQueryEvaluatorFactoryのapplyメソッドを使用して、QueryEvaluatorを作成します。

(12/8 追記 パラメータdbhostsに渡すのは、List[String]です。そのままMySQLJDBCドライバに渡されます。)

  lazy val evaluator = factory(
    dbhosts = List("192.168.0.1:3306", "192.168.0.2:3306"),
    dbname = "dbname",
    username = "username",
    password = "password",
    urlOptions = "urloption" )
クエリの実行

先ほど作成したQueryEvaluatorを使ってクエリを実行します。以下のようにSQLを直接書いて、直感的に利用することが出来ます。

  case class User(userId : Long, serviceId : Int, userName : String)

  // 1レコードのSELECT 戻り値はOption型になる
  val oneUser : Option[User] = evaluator.selectOne("SELECT user_id, service_id, user_name FROM users WHERE user_id = ?", userId) { row =>
    User(row.getLong("user_id"), row.getString("user_name"))
  }
  
  // 複数レコードのSELECT 戻り値はSeq型になる
  val users : Seq[User] = evaluator.select("SELECT user_id, service_id, user_name FROM users WHERE user_id = ?", userId) { row =>
    User(row.getLong("user_id"), row.getString("user_name"))
  }
  
  // UPDATE/DELETE 戻り値は影響のあった行数 Int型になる
  val excuteRet : Int = evaluator.execute("DELETE FROM users WHERE user_id = ? ", userId)
  
  // INSERT 戻り値はauto incrementの結果値 Long型になる
  // executeを実行した後、SELECT LAST_INSERT_ID() を実行する仕様です
  val ret : Int = evaluator.insert("INSERT INTO users(user_id, user_name ) VALUES(?, ?)", 123, "hito_asa")

以上です!

(12/13 修正 INSERTとUPDATE/DELETEの説明が一緒になっていたのを修正しました。INSERTはauto incrementの結果値を返すようです)