ScalikeJDBCでページネーションに必要なクエリを発行する
ScalikeJDBCを使ってページネーションするにはどうすればよいか調べました。 DBはMySQLを使用します。
MySQLで発行するクエリ
SELECT SQL_CALC_FOUND_ROWS * -- LIMIT を付けなかった場合に返されるはずの行数を知るためのオプション FROM users LIMIT 10 -- 取得する行数を制限する OFFSET 5; -- 開始位置(行数) SELECT FOUND_ROWS(); -- 直前に実行したクエリの行数を取得する
ポイント:FOUND_ROWS()
直前に実行したクエリの行数を取得する
直前に実行したクエリにSQL_CALC_FOUND_ROWS
が指定されていた場合、「LIMIT を付けなかった場合に返されるはずの行数」を取得できる
ScalikeJDBCで書く
DSLを使って書くとこんな感じ
withSQL { select(sqls"sql_calc_found_rows *") .from(User as u) .limit(10) .offset(5) }.map(User(u)).single.apply() SQL("select found_rows() as total").map(_.long("total")).single.apply()
ハマったところ
メソッド内で上のコードを実行すると、SQL操作ごとに新しいセッションを開始するためか、select found_rows()
のクエリで直前のクエリを認識できない(totalCount
が1件になる)
def totalCount(implicit session: DBSession = AutoSession): Option[Long] = { withSQL... } res: Option[Long] = Some(1) // 1件になってしまう
解決策:DB.readOnly
で囲えば正しい結果を取得できた
def totalCount: Option[Long] = { DB readOnly { implicit s => withSQL... } } res: Option[Long] = Some(3) // 正しい結果がとれる
--- 12/25 追記 ---
Call back function after list.apply · Issue #63 · scalikejdbc/scalikejdbc · GitHub
AutoSession starts new session for each SQL operation
ScalikeJDBCの AutoSession
はSQL操作ごとに新しいセッションを開始する仕様とのことなので、
ReadOnlySession
を使ってクエリ実行後に session.close
する方法なら、DB.readOnly
で囲わなくても良さそうです。