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()も視野に入れる)