こんばんは、普段ScalaでMySQL用のライブラリにScalikJDBCを利用していて、
QueryDSLをよく使ったりしているんですが、
ふとMySQL固有のクエリを使いたくなった時に、テーブル名やカラム名はべた書きではなく、
case classに定義しているものを使いたいんだけど、
どれを参照すればいいのか正直わからんってなったので、それで調べたときのメモです。
検証したコードはここに置きました。
github.com
ScalikeJDBC
ScalikeJDBC
QueryDSL
QueryDSL - ScalikeJDBC
こんな感じにScalaコードでSQLを書けます
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
みたいな感じでした。なので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)
}
}
()
}
}
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">
<log4jconfiguration xmlnslog4j="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>
</log4jconfiguration>
まとめ
- ScalikeJDBC素のクエリもわりと簡単に投げれそう
- debugログ便利