#1. はじめに
これは、私がScalaを勉強している過程で、オープンソースのコードを読んでみようと思い、色々調べた事を記載しています。私は、これまでまともにオープソースのコードを読んでおらず、業務でもプログラミンをほぼ書かなくなっている者です。が、なるべく初心者でもソースコードを読むきっかけになればと思い書きました。ScalikeJDBCを選択したのは、元々ドキュメントが充実しており、簡単なサンプルアプリなら作ることが出来たので、中身に興味を持てたためです。興味は持てたのですが、なかなか理解出来ない書き方等があり、苦労もありましたが、読む事がScalaの理解にも繋がりました。まずは、主要なクラスの紹介から入って、細かいところで気付いた箇所を説明したいと思います。
以下からコードを幾つか参照させていただいています。
https://github.com/scalikejdbc/scalikejdbc
#2. コネクション
object のDB が主要クラスとなります。DBは、trait である DBConnectionを継承しています。DBConnection は、LogSupport、LoanPattern、AutoCloseableを継承しており、java.sql.Connectionの管理を担当しています。が、DBを通じて、DBConnectionの関数を呼び出しています。
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 を返す』関数が実行されます。
/**
* 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)
}
}
/**
* 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のフレームワーク』が色々存在して、それを隠蔽する形で利用できるようにしてある事が理解できれば、すんなりわかった気がします。
デフォルトは、commons-dbcp2ですが、他のサイトで確認していると、HikariCPが圧倒的に処理量が多いとの記載がありました(が実際に確認はできていませんのでご注意下さい)。
#4. 気付いたところ
読んでいて気づきがあったところを書いてみたいと思います。
##メソッドの最後のthis
最初読み始めた時は、setterっぽいメソッドでどうして最後にthisを返すのか、意味がわかっていませんでした。
/**
* 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 本体のコードでも見ることができる書き方です。
override def takeRight(n: Int): Vector[A] = {
if (n <= 0)
Vector.empty
else if (endIndex - n > startIndex)
dropFront0(endIndex - n)
else
this
}
##=> ロケットシンボル
match case だとか、関数リテラルだとかは理解出来ているつもりだったのですが、以下の構文はすぐにはピンと来ませんでした。
/**
* 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"まわりの理解にチャレンジします。