こんばんは、普段ScalaでMySQL用のライブラリにScalikJDBCを利用していて、
QueryDSLをよく使ったりしているんですが、
ふとMySQL固有のクエリを使いたくなった時に、テーブル名やカラム名はべた書きではなく、
case classに定義しているものを使いたいんだけど、
どれを参照すればいいのか正直わからんってなったので、それで調べたときのメモです。
検証したコードはここに置きました。
ScalikeJDBC
QueryDSL
val u = UniqTestRecord.syntax val rs = withSQL(select.from(UniqTestRecord as u)) .map(rs => (rs.long(u.resultName.id), rs.long(u.resultName.otherId))) .list() .apply()
REPLACE INTO
成約に引っかかるデータがあった場合、データを削除して挿入してくれるやつですね
MySQL :: MySQL 5.6 リファレンスマニュアル :: 13.2.8 REPLACE 構文
テーブル準備
REPLACE INTOを使いたいときは、ユニーク成約がある際などに、
既にレコードがあるなら特に何もせず、レコードがないならレコードをInsertして欲しいみたいな時に使うかと思います。
なので今回は適当にユニーク成約を持ったこんなテーブルを用意しました。
create table uniq_test ( id int unsigned not null, other_id int unsigned not null, unique (id, other_id) );
書いたコード
最終的に書いたコードはこちらです。
scalike-jdbc-mysql-sample/ReplaceInto.scala at master · ara-ta3/scalike-jdbc-mysql-sample · GitHub
冒頭でテーブル名などをcase classに・・・と書きましたが、
case classをOR Mapperのように使えるようにする仕組みがScalikeJDBCには用意されているので、
そのあたりを活用します。
http://scalikejdbc.org/documentation/sql-interpolation.html#sqlsyntaxsupport
こんな感じのcase classとobjectです。
case class UniqTestRecord( id: Long, otherId: Long ) object UniqTestRecord extends MySQLSyntaxSupport[UniqTestRecord] { override val tableName = "uniq_test" } class MySQLSyntaxSupport[T] extends SQLSyntaxSupport[T] { override def connectionPoolName: Symbol = Hello.connection }
困ったこと
最終的に書きたいクエリはこんな感じなのですが、
カラム名とテーブル名を渡す方法がわかりませんでした。
REPLACE INTO uniq_test (id, other_id) VALUES (?, ?);
調べたところ
- テーブル名
UniqTestRecord.table
- SQLSyntaxSupportのtableというプロパティ
- カラム名
UniqTestRecord.column.hoge
- other_idなら
UniqTestRecord.column.otherId
- キャメルケースからスネークケースへの変換はライブラリ側がやってくれます
- http://scalikejdbc.org/documentation/sql-interpolation.html#sqlsyntaxsupport あたりに説明(?)が書いてありますね
- 自前で変えたい場合は
nameConverters
というものを利用するといいみたいです
みたいな感じでした。なのでScalaで書くとこんな感じです。
sql""" REPLACE INTO ${UniqTestRecord.table} (${col.id}, ${col.otherId}) VALUES (?, ?) """
最終的に書いたコードがこちらです。
mainの部分はREPLACE INTOのクエリを2度発行して、
最後にSELECT *を1度投げるという流れです。
package com.ru.waka import scalikejdbc._ import scala.util.control.Exception.catching object ReplaceInto { def main(args: Array[String]): Unit = { val data1 = Seq( Seq(1, 2) ) val data2 = Seq( Seq(1, 2), Seq(2, 3) ) val connection = Hello.connection val col = UniqTestRecord.column val s = sql""" REPLACE INTO ${UniqTestRecord.table} (${col.id}, ${col.otherId}) VALUES (?, ?) """ NamedDB(connection) localTx { implicit session => catching(classOf[Throwable]) either { s.batch(data1: _*).apply() s.batch(data2: _*).apply() val u = UniqTestRecord.syntax val rs = withSQL(select.from(UniqTestRecord as u)) .map(rs => (rs.long(u.resultName.id), rs.long(u.resultName.otherId))) .list() .apply() println(rs) // List((1,2), (2,3)) } } () } } case class UniqTestRecord( id: Long, otherId: Long ) object UniqTestRecord extends MySQLSyntaxSupport[UniqTestRecord] { override val tableName = "uniq_test" }
実際に実行してみましょう。
[info] Running com.ru.waka.ReplaceInto [DEBUG] [2019/06/14 19:46:50.151] [run-main-0] - Registered connection pool : ConnectionPool(url:jdbc:mysql://127.0.0.1:3306/test?characterEncoding=UTF-8, user:root) using factory : <default> [DEBUG] [2019/06/14 19:46:50.533] [run-main-0] - SQL execution completed [SQL Execution] REPLACE INTO uniq_test (id, other_id) VALUES (1, 2); (2 ms) [Stack Trace] ... com.ru.waka.ReplaceInto$.$anonfun$main$2(ReplaceInto.scala:24) scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:23) scala.util.control.Exception$Catch.$anonfun$either$1(Exception.scala:252) scala.util.control.Exception$Catch.apply(Exception.scala:228) scala.util.control.Exception$Catch.either(Exception.scala:252) com.ru.waka.ReplaceInto$.$anonfun$main$1(ReplaceInto.scala:23) scalikejdbc.DBConnection.$anonfun$localTx$3(DBConnection.scala:335) scalikejdbc.DBConnection.rollbackIfThrowable(DBConnection.scala:300) scalikejdbc.DBConnection.localTx(DBConnection.scala:329) scalikejdbc.DBConnection.localTx$(DBConnection.scala:324) scalikejdbc.NamedDB.localTx(NamedDB.scala:20) com.ru.waka.ReplaceInto$.main(ReplaceInto.scala:22) com.ru.waka.ReplaceInto.main(ReplaceInto.scala) java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ... [DEBUG] [2019/06/14 19:46:50.540] [run-main-0] - SQL execution completed [SQL Execution] REPLACE INTO uniq_test (id, other_id) VALUES (1, 2); REPLACE INTO uniq_test (id, other_id) VALUES (2, 3); (3 ms) [Stack Trace] ... com.ru.waka.ReplaceInto$.$anonfun$main$2(ReplaceInto.scala:25) scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:23) scala.util.control.Exception$Catch.$anonfun$either$1(Exception.scala:252) scala.util.control.Exception$Catch.apply(Exception.scala:228) scala.util.control.Exception$Catch.either(Exception.scala:252) com.ru.waka.ReplaceInto$.$anonfun$main$1(ReplaceInto.scala:23) scalikejdbc.DBConnection.$anonfun$localTx$3(DBConnection.scala:335) scalikejdbc.DBConnection.rollbackIfThrowable(DBConnection.scala:300) scalikejdbc.DBConnection.localTx(DBConnection.scala:329) scalikejdbc.DBConnection.localTx$(DBConnection.scala:324) scalikejdbc.NamedDB.localTx(NamedDB.scala:20) com.ru.waka.ReplaceInto$.main(ReplaceInto.scala:22) com.ru.waka.ReplaceInto.main(ReplaceInto.scala) java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ... [DEBUG] [2019/06/14 19:46:50.574] [run-main-0] - SQL execution completed [SQL Execution] select uniq_test.id as i_on_uniq_test, uniq_test.other_id as oi_on_uniq_test from uniq_test; (2 ms) [Stack Trace] ... com.ru.waka.ReplaceInto$.$anonfun$main$2(ReplaceInto.scala:31) scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:23) scala.util.control.Exception$Catch.$anonfun$either$1(Exception.scala:252) scala.util.control.Exception$Catch.apply(Exception.scala:228) scala.util.control.Exception$Catch.either(Exception.scala:252) com.ru.waka.ReplaceInto$.$anonfun$main$1(ReplaceInto.scala:23) scalikejdbc.DBConnection.$anonfun$localTx$3(DBConnection.scala:335) scalikejdbc.DBConnection.rollbackIfThrowable(DBConnection.scala:300) scalikejdbc.DBConnection.localTx(DBConnection.scala:329) scalikejdbc.DBConnection.localTx$(DBConnection.scala:324) scalikejdbc.NamedDB.localTx(NamedDB.scala:20) com.ru.waka.ReplaceInto$.main(ReplaceInto.scala:22) com.ru.waka.ReplaceInto.main(ReplaceInto.scala) java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ... List((1,2), (2,3))
debugログからREPLACE INTOのクエリが走ってるのがわかりますね。
よかったよかった。
ちなみにdebugログが出ていますが、
これは依存に "org.slf4j" % "slf4j-log4j12" % "1.7.25"
を追加して、
resourcesディレクトリにlog4j.xmlを追加してあげるとライブラリが流れたクエリをdebugログで出してくれます。
log4j.xmlのサンプル
どっかから丸コピしたので全く考えられたものではないです(突っ込まないで)
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"> <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/" > <appender name="stdout" class="org.apache.log4j.ConsoleAppender"> <param name="target" value="System.out" /> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="[%-5p] [%d{yyyy/MM/dd HH:mm:ss.SSS}] [%t] - %m%n" /> </layout> </appender> <root> <level value="debug" /> <appender-ref ref="stdout"/> </root> </log4j:configuration>
まとめ
- ScalikeJDBC素のクエリもわりと簡単に投げれそう
- debugログ便利