S-JIS[2011-03-05] 変更履歴
ScalaでJDBCを使ってテーブルにinsertしてみる。
まずは何の変哲も無い単なるinsert文。
val sql = """insert into EMP (EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM, DEPTNO) values(8001, 'name8001', 'job8001', 0, '2011-03-05', 10000, NULL, 20)""" val st = conn.createStatement st.execute(sql) st.close conn.commit
※日付型にデータを入れるときはOracleだとto_date関数を使うべきだが、ここでは手抜きして文字列を直接指定…
次いで、PreparedStatementにしてみる。
ただ単に値をセットしていくだけではつまらないので、項目名と値をペアにして定義するようにしてみる。
(項目名一覧と値一覧が分離しているのは分かりづらいし、順序を変えたい場合等に2箇所に手を入れることになるから、一元管理したい)
val vs = List(("EMPNO", 8002), ("ENAME", "name8002"), …)
そういえばペア(値が2個のタプル)を使うなら「->」が使えるので、そちらの方が分かりやすそう。
insertで使う目的だから、左側を値にして右側を項目名にする方が“らしく見える”かな?
val NULL:Any = null val vs = List(8002->"EMPNO", "name8002"->"ENAME", "job8002"->"JOB", 0->"MGR", "2011-03-05"->"HIREDATE", 10000->"SAL", NULL->"COMM", 20->"DEPTNO")
※「null->"COMM"
」はエラーになるので、「(null, "COMM")
」と書くか、Null型でないnullを定義(NULL:Anyとか)して「->」を使う。
val sql = "insert into EMP (" + vs.map(_._2).mkString(", ") + ") values (" + vs.map(_=>'?').mkString(", ") + ")" |
文字列の結合(+演算子)を使う場合、REPL上では式の途中で改行できない。 |
val sql = """insert into EMP ( """ + vs.map(_._2).mkString(", ") + """ ) values ( """ + vs.map(_=>'?').mkString(", ") + """ )""" |
複数行リテラルを使って文字列内で改行する例。 |
val sql = <s>insert into EMP ( { vs.map(_._2).mkString(", ") } ) values ( { vs.map(_=>'?').mkString(", ") } )</s>.text |
XMLリテラルを使って文字列内で改行する例。 |
val st = conn.prepareStatement(sql)
var n = 1 vs.foreach{ t => val v = t._1 st.setObject(n, v) n += 1 } |
インデックスを計算する変数を外に用意する方法。 varはあんまり使いたくないけど…。 |
vs.zipWithIndex.foreach{ t => val ((v,_), n) = t st.setObject(n+1, v) } |
zipWithIndexは、要素とインデックスのペアを作り出す。 |
vs.zipWithIndex.foreach{ t => val v = t._1._1 val n = t._2 st.setObject(n+1, v) } |
|
vs.zipWithIndex.foreach(t => st.setObject(t._2 +1, t._1._1)) |
|
vs.zipWithIndex.foreach{ case ((v,_), n) => st.setObject(n+1, v) } |
|
vs.foldLeft(1){ (n,t) => st.setObject(n, t._1) n + 1 } |
foldLeftでインデックスを渡せる(計算できる)けど、 こういう使い方は良くないだろうなぁ(苦笑) きっと、foldLeftは副作用を起こす処理で使うものじゃない。 |
st.executeUpdate st.close conn.commit
上記のリストの持ち方では、複数データをインサートしたい場合には、毎回項目名をリストに入れなきゃいけなくて、いまいち。
値を入れるバリューオブジェクト的なクラスを作って、そこに入れたデータを扱えると便利そう。
Scalaでバリューオブジェクト的なクラスと言えば、ケースクラス。
case class Emp( empno: Int, ename: String, job: String, mgr: Int, hiredate: Date, sal: Double, comm: Double, deptno: Int ) def sdate(y:Int, m:Int, d:Int) = { val c = java.util.Calendar.getInstance c.clear c.set(y, m-1, d) new Date(c.getTimeInMillis) } val e1 = Emp(8003, "name8003", "job8003", 0, sdate(2011,3,5), 10000, 0, 20) val cs = List("EMPNO", "ENAME", "JOB", "MGR", "HIREDATE", "SAL", "COMM", "DEPTNO") val sql = "insert into EMP (" + cs.mkString(", ") + ") values (" + cs.map(_=>'?').mkString(", ") + ")" val st = conn.prepareStatement(sql) // 個々の値をセット st.setInt(1, e1.empno) st.setString(2, e1.ename) st.setString(3, e1.job) st.setInt(4, e1.mgr) st.setDate(5, e1.hiredate) st.setDouble(6, e1.sal) st.setDouble(7, e1.comm) st.setInt(8, e1.deptno) st.executeUpdate st.close conn.commit
さて、出来るには出来たけど、いちいちPreparedStatementに値をセットしていく(コーディングをする)のが面倒。
そういえばケースクラスには値をイテレーターで取得する方法がある。
// 個々の値をセット e1.productIterator.zipWithIndex.foreach{ case(v,n) => st.setObject(n+1, v) }
どうせならテーブルの項目名も取りたいところだが、ケースクラスのフィールド名一覧を取得するメソッドは無いんだよなぁ。
Enumerationではリフレクションを使って名前を取得しているので、真似てみよう。
Scalaではフィールドの値を取得するように見せかけて実際にはフィールド名と同名のメソッドが定義されているので、メソッド一覧を取得してやればいい。
ただ、toString()だのhashCode()だのといった関係ないメソッドまで取れてしまうので、選別する必要はある。
今回はテーブルの項目名が相手なので、全部大文字のメソッドがあったら項目名とみなすことにしよう。
case class Emp(
EMPNO: Int,
ENAME: String = null,
JOB: String = null,
MGR: java.lang.Integer = null,
HIREDATE: Date = null,
SAL: java.lang.Double = null,
COMM: java.lang.Double = null,
DEPTNO: java.lang.Integer = null
)
//EMPNOはNOT NULL項目だがその他はNULL可なので、デフォルト値をnullにしてみた。
//数値項目にもnullを入れたいので、ScalaのIntやDoubleでなく、Javaのラッパークラスにしてみた。ラッパーでも、暗黙変換があるので普通に数値を代入できる。
val e2 = Emp(8004, "name8004", "job8004", 0, sdate(2011,3,5), SAL=10000, DEPTNO=20)
// メソッド一覧取得
val ms = classOf[Emp].getMethods.filter(_.getName.forall(_.isUpper))
val sql = "insert into EMP (" + ms.map(_.getName).mkString(", ") + ") values (" + Array.fill(ms.size)('?').mkString(", ") + ")"
val st = conn.prepareStatement(sql)
// 個々の値をセット
ms.zipWithIndex.foreach{ case(m,n) => st.setObject(n+1, m.invoke(e2)) }
st.executeUpdate
st.close
conn.commit
あ、「_」を項目名に含めることも考えるなら、isUpperを呼ぶんじゃなくてtoUpperCaseで変換して比較した方がいいかも。
// メソッド一覧取得 val ms = classOf[Emp].getMethods.filter{m => val s = m.getName; s==s.toUpperCase}
あと、Empクラスの複数データを作りたい場合は 都度新しいインスタンスを作るのが常套手段だと思うけど、
一部分だけ変えたいなら、copyメソッドを使って新しいインスタンスを生成するか、
ケースクラス定義時に各パラメーターに「var」を付けて、後から変更できるようにすればいい。
(JDBCの実行方法的には、executeBatch()も視野に入れる)