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の AutoSessionSQL操作ごとに新しいセッションを開始する仕様とのことなので、 ReadOnlySession を使ってクエリ実行後に session.close する方法なら、DB.readOnly で囲わなくても良さそうです。