BeanTalkクラスを前回紹介しました。
ActiveRecordに近づけるために、find関数で複数レコードを扱えるように機能拡張しました。
クラス名をBeansTalkと複数形にしました。
bean stalkと単語の区切りを変えると、豆の木(茎)になり「ジャックと豆の木」という意味になることに気が付きました。
ActiveRecordについていろいろなことを書きましたが、正直、Ruby on railsのActiveRecordでコードを書いたことがないので、
頓珍漢なことを書いていてもActiveRecordフリークの方々ご容赦ください。
とわいえ、ActiveRecordのfind_by_sqlの逆を行くわけで、長年SQL文を書いて生活の糧を得ていた者としては真逆の行為です。
しかし、簡単なWEBアプリケーションを書くなら、これで十分という気持ちです。
複数レコードを扱えるようにしたので、N:Nの関係を表現できます。
BlogやSNSを実装するならこれで十分かな?
package seedo.database import scala.collection.mutable.{HashMap,LinkedHashMap,HashSet,ArrayBuffer} import scala.reflect._ import scala.collection.mutable.Map import seedo._ class BeansTalk[T] (implicit m : ClassManifest[T]) { var beanObj = m.erasure.newInstance().asInstanceOf[T] // TのclassのBean を生成 var joinEntity = Map.empty[String,Array[AnyRef]] // joinしているテーブルのレコード val beanInfo = new FromBean(beanObj.asInstanceOf[AnyRef]) // Beanのアノテーション情報からテーブル情報を読み取る var praimaryKey : String = null // プライマリキー if(beanInfo.praimaryKey.length == 1) praimaryKey = beanInfo.praimaryKey(0) var beanObjs:Array[T] = null // 複数レコードが検索されたときに格納 var joinEntities:Array[Map[String,Array[AnyRef]]] = null // 複数レコードのときにJOINした表データを格納 var column:String = "*" // SELECTするときに読み込むカラムを指定 var key:String = null // Order by句 のソートキー var condition:String = null // Where句の条件 var values:Array[Any] = null // 条件に指定する値 // SELECT文の構成要素を初期化 def clean:Unit = { column = "*" key = null condition = null values = null } var db:Db = null // Dbクラス var tran = true // トランザクション制御する。外部からDbクラスのオブジェクトが渡されたときはトランザクションモード(false) def bean : T = beanObj def entity : T = beanObj def beans : Array[T] = beanObjs var joins = Map.empty[String,List[List[String]]] // join先のBean名とjoinの条件 /** * Joinしているテーブルのレコードを返す */ def getjoinEntity(joinBeanName:String) :Option[Array[AnyRef]] = { joinEntity.get(joinBeanName) } // join has_one / has_many def addJoin(beanName:String,on:List[String]*) : Unit = { joins.put(beanName,on.toList) } def removeJoin(beanName:String) : Unit = { joins.remove(beanName) joinEntity.remove(beanName) } def removeAllJoin() : Unit = { joins.clear joinEntity.clear } def setDb(dbx:Db):Unit = { db = dbx tran = false } // SELECT One record. praimaryKey is some columns. def getEntity(pk:Any*) : T = { if(tran) db = new Db if(column==null) column = "*" var sql:DL = db val result = db.selectBean(beanObj.asInstanceOf[AnyRef],pk:_*) if(tran){ db.close db = null } joinsRecords(beanObj, joinEntity) clean return result.asInstanceOf[T] } // set bean object. def setEntity(e:T) : Unit = { beanObj = e } // select One record def find : T = { if(tran) db = new Db if(column==null) column = "*" val sql:DL = db val result = db.selectBean(beanObj.asInstanceOf[AnyRef]) if(tran){ db.close db = null } joinsRecords(beanObj, joinEntity) clean return result.asInstanceOf[T] } // gen 'in' sentence. private def qu(no:Int):String = { var str = "" for(i <- 1 to no){ if(i != 1){ str += "," } str += "?" } str } // select join records. private def joinsRecords(beanO:T,joinE:Map[String,Array[AnyRef]]) :Unit = { joinE.clear joins.foreach(j => { val clazz = beanO.getClass val k = j._2 var whereStr = "" val whereVal = new ArrayBuffer[Any] var columnStr = "*" var n = 0 k.foreach(i => { if(i.length > 2){ val methodName = "get" + i(0) // Master側のJOINカラム BENAからそのカラムの値を取り出す val v = clazz.getMethod(methodName).invoke(beanO) whereVal += v if(n != 0){ whereStr += " and " } whereStr += i(2) + i(1) + "?" // Detail側のJOINカラム 抽出条件を作る。i(2)は、JOIN側のカラム名、i(1)は比較演算子「'=','<>','<','>','<=','>='」 if(i.length > 3){ columnStr = i(3) // Detail側のSELECTしたときに取るカラム } }else{ throw new Exception("Join sentence should be 'Master.column name' + '=' + 'Detail.column name'. <- " + i.toString) } n += 1 }) val dl = db.select(columnStr) if(whereVal.length > 0){ dl.whereParam(whereStr, whereVal.toArray) } val result = db.executeQueryBean(j._1) joinE.put(j._1,result.toArray) }) } // SELECT some records. praimaryKey is one column. def find(ids:Any*) : Array[T] = { joinEntities = null if(tran) db = new Db if(column==null) column = "*" var sql:DL = db.select(column).from(beanInfo.table) if(ids != null) { sql.where(praimaryKey + " in (" + qu(ids.length) + ")",ids:_*) } if(key != null) { sql.orderBy(key) } val beanObjsBuf = db.executeQuery(beanObj.asInstanceOf[AnyRef]).asInstanceOf[ArrayBuffer[T]] if(beanObjsBuf.length == 1){ // 検索結果が1の場合は、自身のbeanObjを入れ替える beanObj = beanObjsBuf(0) joinsRecords(beanObj, joinEntity) } else { joinEntities = new Array[Map[String,Array[AnyRef]]](beanObjsBuf.length) var i = 0 beanObjsBuf.foreach(v => { val je = Map.empty[String,Array[AnyRef]] joinsRecords(v, je) joinEntities(i) = je i += 1 }) } if(tran){ db.close db = null } clean beanObjs = beanObjsBuf.toArray return beanObjs } // SELECT WHERE句で条件をレコードを絞り込む def findAll : Array[T] = { if(tran) db = new Db var sql:DL = db.select(column).from(beanInfo.table) if(condition != null){ sql.whereParam(condition,values) } if(key != null) { sql.orderBy(key) } val beanObjsBuf = db.executeQuery(beanObj.asInstanceOf[AnyRef]).asInstanceOf[ArrayBuffer[T]] if(tran){ db.close db = null } clean beanObjs = beanObjsBuf.toArray return beanObjs } // UPDATE one record def update : Int = { if(tran) db = new Db try { return db.updateBean(beanObj.asInstanceOf[AnyRef]) } catch { case e:Exception => { db.rollback throw e } } finally { if(tran){ db.commit db.close db = null } clean } } def save : Int = { update } // DELETE one record def delete : Int = { if(tran) db = new Db try { return db.deleteBean(beanObj.asInstanceOf[AnyRef]) } catch { case e:Exception => { db.rollback throw e } } finally { if(tran){ db.commit db.close db = null } clean } } // DELET some records. praimaryKey is one column. def delete(ids:Any*) : Int = { if(tran) db = new Db try { var sql:DL = db.deleteFrom(beanInfo.table) if(ids != null) { sql.where(praimaryKey + " in (" + qu(ids.length) + ")",ids:_*) } return db.execute.getResultSize } catch { case e:Exception => { db.rollback throw e } } finally { if(tran){ db.commit db.close db = null } clean } } // INSERT one record def create : Int = { if(tran) db = new Db try { return db.insertBean(beanObj.asInstanceOf[AnyRef]) } catch { case e:Exception => { db.rollback throw e } } finally { if(tran){ db.commit db.close db = null } clean } } // SQL SELECT sentence def column(col:String) :BeansTalk[T] = { this.column = col this } def where(condition:String,values:Any*) :BeansTalk[T] = { this.condition = condition this.values = values.toArray this } def conditions(condition:String,values:Any*) :BeansTalk[T] = { this.condition = condition this.values = values.toArray this } def orderBy(k:String) :BeansTalk[T] = { this.key = k this } def commit : BeansTalk[T] = { if(!tran) db.commit this } def rollback : BeansTalk[T] = { if(!tran) db.rollback this } def dump : Unit = { println(beanObj.toString) if(joinEntity != null) { joinEntity.foreach(w => { println(" " + w._1) w._2.foreach(x =>{ println(" " + x) }) }) } } def dumps : Unit = { var i = 0 if(beanObjs == null) return beanObjs.foreach(r =>{ println(r.toString) if(joinEntities != null) { joinEntities(i).foreach(w =>{ println(" " + w._1) w._2.foreach(x =>{ println(" " + x) }) }) } i += 1 }) } }
テストコードです
find関数に従業員IDを3つの引数でを渡します。
@Test def test1 { println("--- TSET 1 ---") beanobj.addJoin("scott.bean.DEPT",List("DEPTNO","=","DEPTNO")) val r = beanobj.find(7788,7698,7934) beanobj.dumps }
実行結果
--- TSET 1 --- 7698,BLAKE,MANAGER,7839,1981-05-01 00:00:00.0,2850,,30 scott.bean.DEPT 30,SALES,CHICAGO 7788,SCOTT,ANALYST,7566,1987-04-19 00:00:00.0,3000,,20 scott.bean.DEPT 20,RESEARCH,DALLAS 7934,MILLER,CLERK,7782,1982-01-23 00:00:00.0,1300,,10 scott.bean.DEPT 10,ACCOUNTING,NEW YORK --- End TEST ---
テストコードその2
テスト2-1では、
where関数で検索する条件としてENAMEがSで始まる人をSELECTして、
orderBy関数によってENAMEの順序でソートします。
テスト2-2では、
where関数により給与が1500以上の条件で、
column関数でSELECTするカラムを”empno,sal,ename”と指定します。
orderBy関数によって給与順にソート。
@Test def test2 { println("--- TSET 2-1 ---") val r1 = beanobj.where("ename like ?","S%").orderBy("ename").findAll r1.foreach(v => println(v.toString)) println("--- TSET 2-2 ---") val r2 = beanobj.column("empno,sal,ename").where("sal > ?",1500).orderBy("sal").findAll r2.foreach(v => println(v.toString)) }
実行結果
--- TSET 2-1 --- 7788,SCOTT,ANALYST,7566,1987-04-19 00:00:00.0,3000,,20 7369,SMITH,CLERK,7902,1980-12-17 00:00:00.0,800,,20
--- TSET 2-2 --- 7499,ALLEN,1600 7782,CLARK,2450 7698,BLAKE,2850 7566,JONES,2975 7788,SCOTT,3000 7902,FORD,3000 7839,KING,5000 --- End TEST ---
BeansTalkクラスは、seedo-1.2.4.jarに格納しました。
動かしてみたい方は、
seedo-1.2.4.jarはこのリンクからダウンロードできます。
BEANを使うために以下の2つのJARもダウンロードしてください。
scott.jar と jpa.jar
MySQLの環境は事前に作ってください。