Scala
scalikeJDBC

ScalikeJDBC 3.0 コードリーディング(コネクション関係)

1. はじめに

 これは、私がScalaを勉強している過程で、オープンソースのコードを読んでみようと思い、色々調べた事を記載しています。私は、これまでまともにオープソースのコードを読んでおらず、業務でもプログラミンをほぼ書かなくなっている者です。が、なるべく初心者でもソースコードを読むきっかけになればと思い書きました。ScalikeJDBCを選択したのは、元々ドキュメントが充実しており、簡単なサンプルアプリなら作ることが出来たので、中身に興味を持てたためです。興味は持てたのですが、なかなか理解出来ない書き方等があり、苦労もありましたが、読む事がScalaの理解にも繋がりました。まずは、主要なクラスの紹介から入って、細かいところで気付いた箇所を説明したいと思います。

以下からコードを幾つか参照させていただいています。
https://github.com/scalikejdbc/scalikejdbc

2. コネクション

 object のDB が主要クラスとなります。DBは、trait である DBConnectionを継承しています。DBConnection は、LogSupport、LoanPattern、AutoCloseableを継承しており、java.sql.Connectionの管理を担当しています。が、DBを通じて、DBConnectionの関数を呼び出しています。
connection_1.jpg

DBのメソッドを簡単に紹介します。

メソッド 概略
readOnly 『DBSessionを受け取りAを返す関数』を引数に取る。読み取り専用の処理。
autoCommit 同上で、コミット(ロールバック)を自動的に実施。
localTx 同一ブロックのSQLを同一トランザクション管理できます。失敗すれば全てロールバック。
futureLocalTx Futureでラップした結果を受け取り、それぞれの処理をひとつのトランザクションとして管理します。
withinTx 呼び出す側にてトランザクション管理(begin,commit/rollback)が必要です。

他にも、getAllColumnsやgetColumnNames、getTableと言ったメソッドも持っている。

親のtrait である DBConnection がjava.sql.Connectionを保持していますが、その取得は点線で下にひも付けている、ConnectionPoolContextを利用しています(ScalikeJDBCのコネクションプールは後述)。
このDBConnectionもreadOnly、autoCommit等々と同名のメソッドを持っています。大きな違いは、implicit パラメータが無いところです。また、readOnlyの中では『DBSessionを引数に持ち、A を返す』関数が実行されます。

DB.scala(object)

  /**
   * Begins a read-only block easily with ConnectionPool.
   *
   * @param execution execution
   * @param context connection pool context
   * @tparam A return type
   * @return result value
   */
  def readOnly[A](execution: DBSession => A)(implicit context: CPContext = NoCPContext, settings: SettingsProvider = SettingsProvider.default): A = {
    val cp = connectionPool(context)
    using(cp.borrow()) { conn =>
      DB(conn, cp.connectionAttributes, settings).autoClose(false).readOnly(execution)
    }
  }
DBConnection.scala
  /**
   * Provides read-only session block.
   * @param execution block
   * @tparam A  return type
   * @return result value
   */
  def readOnly[A](execution: DBSession => A): A = {
    if (autoCloseEnabled) using(conn)(_ => execution(readOnlySession()))
    else execution(readOnlySession())
  }

この『DBSessionを受け取る関数』を引き渡しているところは、他のメソッドでも見られる特徴です。このおかげで、以下に記載があるように、お決まりの長い記述をコーディングしなくても済んでいます。
http://scalikejdbc.org/documentation/connection-pool.html

3. コネクションプール

コネクションプールに関しては、サンプルにあるようにConnectionPoolのsingletonで初期化が走ります。実際には内部でaddが呼ばれて初期化が色々走っています。
当初は、XXXXPoolクラスが沢山あって、ポイントが理解できなかったのですが、『ConnectionPoolのフレームワーク』が色々存在して、それを隠蔽する形で利用できるようにしてある事が理解できれば、すんなりわかった気がします。

connectionpool_1.jpg

デフォルトは、commons-dbcp2ですが、他のサイトで確認していると、HikariCPが圧倒的に処理量が多いとの記載がありました(が実際に確認はできていませんのでご注意下さい)。

4. 気付いたところ

読んでいて気づきがあったところを書いてみたいと思います。

メソッドの最後のthis

最初読み始めた時は、setterっぽいメソッドでどうして最後にthisを返すのか、意味がわかっていませんでした。

DBConnection.scala
  /**
   * Switches auto close mode.
   * @param autoClose auto close enabled if true
   */
  def autoClose(autoClose: Boolean): DBConnection = {
    this.autoCloseEnabled = autoClose
    this
  }

これは、以下の様に文章の様にコーディングするためだとようやく気づく事ができました。

DB(conn, cp.connectionAttributes, settings).autoClose(false).readOnly(execution)

Scala 本体のコードでも見ることができる書き方です。

Vector.scala
  override def takeRight(n: Int): Vector[A] = {
    if (n <= 0)
      Vector.empty
    else if (endIndex - n > startIndex)
      dropFront0(endIndex - n)
    else
      this
  }

=> ロケットシンボル

match case だとか、関数リテラルだとかは理解出来ているつもりだったのですが、以下の構文はすぐにはピンと来ませんでした。

DBConnection.scala
  /**
   * Provides read-only session block.
   * @param execution block
   * @tparam A  return type
   * @return result value
   */
  def readOnlyWithConnection[A](execution: Connection => A): A = {
    readOnly(s => execution(s.conn))
  }

ずっと s を探していました。。。どこで渡されるんだろうって。
これも関数リテラルで、『DBSessionを受け取る関数』をパラメータにして、readOnlyを呼んでいる事が理解できて、ようやく読めた気がしました。
以下みたいに、型と()を記述して、少し納得できました。

readOnly((s: DBSession) => execution(s.conn))

クラス図は意外に大事

今まであまりソースコードをしっかり読んで来なかったので、ファイルがずらっと並んでいてもずっとモヤモヤしてました。処理を追っていって頭では理解できたつもりでも、もう一度見直すとモヤモヤ感からやり直しの日々でした。
それから抜け出すために、図に書きだして見ると、まずはその図が頭に浮かぶようになりました。自分はやはり図からコードをイメージするタイプなようです。
今回、書いた図(とこの文章)が初めてScalikeJDBCのソースコードを読む人の少しでも手助けになればと思います。
あと、次は"SQL"まわりの理解にチャレンジします。