ActiveRecordのようにDB操作ということで、前回コードを書いてみましたが、EMP表に依存したクラスになってしまいました。
新しいコードはテーブルに依存しない汎用のBeanTalkクラスを書いてみました。
このクラスはかなり複雑な内容になってます。
implicitという表記を使っています。
リフレクションするときの定番の書き方です。
もっと綺麗な表記もあるのでしょうがこれ以上わかりにくくしたくないのでこのままにします。
package seedo.database import scala.collection.mutable.{HashMap,LinkedHashMap,HashSet,ArrayBuffer} import scala.reflect._ import scala.collection.mutable.Map import seedo._ class BeanTalk [T] (implicit m : ClassManifest[T]) { var beanObj = m.erasure.newInstance().asInstanceOf[T] // TのclassのBean を生成 var joinEntity = Map.empty[String,Array[AnyRef]] // joinしているテーブルのレコード var column:String = "*" // SELECTするときに読み込むカラムを指定 // SELECT文の構成要素を初期化 def clean:Unit = { column = "*" } var db:Db = null // Dbクラス var tran = true // トランザクション制御する。外部からDbクラスのオブジェクトが渡されたときはトランザクションモード(false) def bean : T = beanObj def entity : T = beanObj 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] } // 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) }) } // 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 } } // 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) :BeanTalk[T] = { this.column = col this } def commit : BeanTalk[T] = { if(!tran) db.commit this } def rollback : BeanTalk[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) }) }) } } }
ActiveRecordでは、1つのクラスが 基本的にDBの1テーブルに対応し、クラスの属性(attribute)は、テーブルの各カラムに対応します。
ActiveRecordのDB操作についての私の見方は、MS-ACCESSのような操作イメージです。
数値型のプライマリーキー・カラムが1つあり、このキーによってレコードを特定します。
UPDATE操作やDELETE操作は、ACCESSの画面で操作するためテーブルのレコードデータをエクセルのシートのような表に読み込んでから操作します。人がPCでマウスで操作しますから当然です。
ACCESSはデータベースシステム自身であり、VBAでそのデータを操作するのであればこれでよいのですが、クライアント・サーバモデルでデータ操作するときに、オブジェクトとしてレコードデータをfindしてからUPDATEやDELETEというのはロジックのわかりやすさという点ではいいのですが、処理としては非効率です。
非手続き型言語のSQLと、RubyやJava、Scalaのような手続き型言語の折り合いの悪さは、リレーショナルデータベースとプログラミング言語の中のインピーダンスミスマッチとして議論され続けています。
話が違う方向に行きそうなので、話を戻すと、
BeanTalkクラスの戦略は、DBの1テーブルは、Beanクラスが対応します。
BeanTalkクラスでは、var beanobj = new BeansTalk[scott.bean.EMP]によってBeanをラッピングして、DB操作を共通化しますがSQL文をプログラマに見せません。
BeanTalkクラスが生成されると、内部にBeanオブジェクトが1つ生成されます。
Dbクラス、Beanを介してのSELECTなどのSQL操作は、SeedoフレームワークにあるDB操作のためのJarにあります。
早速、このクラスのテストコードを見ましょう。
@Beforeの initialize関数でbeanobj = new BeanTalk[scott.bean.EMP]によってEMP表のBeanTalkオブジェクトを生成します。
DEPT表についても、beanobjDept = new BeanTalk[scott.bean.DEPT]でBeanTalkオブジェクトを生成し、JDBCドライバによってデータベース接続を行うdb = new Db(driver, dsn, user, password)を生成し、各BeanTalkオブジェクトにセットします。
このテストコードは、junitなので明示的にDbクラスを生成していますが、TOMCATでBeanTalkクラスを使う場合は、TOMCATのコネクションプールからDB接続を入手しますので、driver,dsn,user,passwordは明示する必要はありません。
package test import org.scalatest.junit.{JUnitSuite,ShouldMatchersForJUnit} import org.junit.{Test,Before,After} import seedo.database._ class BeanTalkTest extends JUnitSuite with ShouldMatchersForJUnit{ val driver = "com.mysql.jdbc.Driver" val dsn = "jdbc:mysql://127.0.0.1:3306/seedo?useUnicode=true&characterEncoding=UTF-8" val user ="scott" val password ="tiger" var db:Db = null var beanobj:BeanTalk[scott.bean.EMP] = null var beanobjDept:BeanTalk[scott.bean.DEPT] = null @Before def initialize() { println("--- Start TSET ---") beanobj = new BeanTalk[scott.bean.EMP] println("getClass=" + beanobj.getClass) beanobjDept = new BeanTalk[scott.bean.DEPT] db = new Db(driver, dsn, user, password) beanobj.setDb(db) beanobjDept.setDb(db) } @Test def test1 { println("--- TSET 1 ---") beanobj.bean.setEMPNO(7788) val r = beanobj.find beanobj.dump } @Test def test2 { println("--- TSET 2 ---") try { beanobj.getEntity(7788) println("getEntity=" + beanobj.entity) beanobj.dump val entityCopy = beanobj.entity.clone println("--update op") beanobj.bean.setDEPTNO(10) beanobj.bean.setSAL(3200) beanobj.update println("--find") beanobj.find beanobj.dump println("---update op") beanobj.bean.setDEPTNO(20) beanobj.bean.setSAL(3000) beanobj.update println("---find") beanobj.find beanobj.dump println("---delete op") beanobj.delete println("---find") val b = beanobj.find if(b != null) beanobj.dump beanobj.setEntity(entityCopy) println("---create op") beanobj.create println("---find") beanobj.find beanobj.dump } catch { case e:Exception => { println("Exception "+e.getMessage) } } } @Test def test3 { println("--- TSET 3 ---") beanobj.bean.setEMPNO(7788) beanobj.addJoin("scott.bean.DEPT",List("DEPTNO","=","DEPTNO","DEPTNO,DNAME,LOC")) beanobj.addJoin("scott.bean.EMP",List("DEPTNO","=","DEPTNO"),List("JOB","=","JOB")) val r = beanobj.find println(r.toString) val s = beanobj.getjoinEntity("scott.bean.DEPT") match { case Some(v) => v.foreach(w => println(" DEPT " + w.toString)) case None => Nil } val s3 = beanobj.getjoinEntity("scott.bean.EMP") match { case Some(v) => v.foreach(w => println(" EMP " + w.toString)) case None => Nil } } @Test def test4 { println("--- TSET 4 ---") beanobj.removeAllJoin beanobj.bean.setEMPNO(7782) val r3 = beanobj.find println(r3.toString) val s4 = beanobj.getjoinEntity("scott.bean.DEPT") match { case Some(v) => v.foreach(w => println(" DEPT " + w.toString)) case None => Nil } val s5 = beanobj.getjoinEntity("scott.bean.EMP") match { case Some(v) => v.foreach(w => println(" EMP " + w.toString)) case None => Nil } } @Test def test5 { println("--- TSET 5 ---") beanobjDept.bean.setDEPTNO(10) beanobjDept.addJoin("scott.bean.EMP",List("DEPTNO","=","DEPTNO")) beanobjDept.addJoin("scott.bean.DEPT",List("DEPTNO","=","DEPTNO")) val r1 = beanobjDept.find println(r1.toString) val s1 = beanobjDept.getjoinEntity("scott.bean.EMP") match { case Some(v) => v.foreach(w => println(" EMP " + w.toString)) case None => Nil } val s2 = beanobjDept.getjoinEntity("scott.bean.DEPT") match { case Some(v) => v.foreach(w => println(" DEPT " + w.toString)) case None => Nil } } @After def dest { db.close println("--- End TEST ---\n") } }
JUNITの実行結果
最初のコードは、EMP表からEMPNOが7788の従業員をSELECTします。
BeanTalkの中でEMP表のBEANに対してEMPNOをSETします。
次にfind関数を呼び出せば、BEANのレコード内容が設定されます。
その内容は、dump関数によってプリントします。
beanobj.bean.setEMPNO(7788) val r = beanobj.find beanobj.dump
7788番のスコットさんがSELECTされています
--- Start TSET --- getClass=class seedo.database.BeanTalk JDBC Driver:MySQL-AB JDBC Driver Version:mysql-connector-java-5.1.8 ( Revision: ${svn.Revision} ) Database:MySQL Version:5.5 --- TSET 1 --- 7788,SCOTT,ANALYST,7566,1987-04-19 00:00:00.0,3000,,20 --- End TEST ---
次のコードは、SELECT,UPDATE,DELETE,INSERTの一連のDB操作です。
先ほどと同様に7788番のレコードをgetEntity関数でSELECTします。なぜfind関数ではないかは置いておいて、
beanobj.entity.clone関数によって7788のBEANの複製を作ります。
beanobj.getEntity(7788) println("getEntity=" + beanobj.entity) beanobj.dump val entityCopy = beanobj.entity.clone
その結果が以下です。
dump関数によってスコットさんのレコードがプリントされています。
--- Start TSET --- getClass=class seedo.database.BeanTalk JDBC Driver:MySQL-AB JDBC Driver Version:mysql-connector-java-5.1.8 ( Revision: ${svn.Revision} ) Database:MySQL Version:5.5 --- TSET 2 --- getEntity=7788,SCOTT,ANALYST,7566,1987-04-19 00:00:00.0,3000,,20 7788,SCOTT,ANALYST,7566,1987-04-19 00:00:00.0,3000,,20
次に、スコットさんの所属を20番から10番に、給料を3000から3200に更新します。BEANのSETTER関数で値を設定し、update関数を実行します。
更新した内容を再び、find関数でSELECTしてdump関数でプリントします。
println("--update op") beanobj.bean.setDEPTNO(10) beanobj.bean.setSAL(3200) beanobj.update println("--find") beanobj.find beanobj.dump
結果は、スコットさんの所属と給料が変更されています。
--update op --find 7788,SCOTT,ANALYST,7566,1987-04-19 00:00:00.0,3200,,10
次に、再び所属と給料を設定してデータを元に戻します
println("---update op") beanobj.bean.setDEPTNO(20) beanobj.bean.setSAL(3000) beanobj.update println("---find") beanobj.find beanobj.dump
データは、元に戻っています。
---update op ---find 7788,SCOTT,ANALYST,7566,1987-04-19 00:00:00.0,3000,,20
次の操作は、
delete関数によってスコットさんのレコードを削除します。
find関数で削除された7788番のレコードを検索します。
println("---delete op") beanobj.delete println("---find") val b = beanobj.find if(b != null) beanobj.dump
結果は、レコードが削除されたのでプリントされません。
find関数は、レコードが検索できなかった場合はnullを返します。
---delete op ---find
beanobj.setEntity(entityCopy)関数によって先ほど作ったBeanクーロン(複製)をbeanobjにセットします。
create関数を呼び出し、EMP表にINSERTします。
beanobj.setEntity(entityCopy) println("---create op") beanobj.create println("---find") beanobj.find beanobj.dump
再びスコットさんのレコードが検索できます。
---create op ---find 7788,SCOTT,ANALYST,7566,1987-04-19 00:00:00.0,3000,,20 --- End TEST ---
この操作では、まったくSQL文は見えません。
マスター・ディテール
3つ目のテストは、マスター・ディテールの1:N関係です。
addJoin関数によってEMP表とDEPT表をSELECTします。
この処理は決して、EMP表とDEPT表をWHERE句でジョイン(結合)してSELECTしているわけではありません。
addJoin関数の
第一引数は、DEPT表用のBEANクラスの名称(scott.bean.DEPT)。
第二引数は、EMP表とDEPT表の結合関係をListによって表します。
関係は、
Listの
第一要素がEMP表のカラム
第二要素が関係式。以下の処理では’=’
第三要素がDEPT表のカラム
第四要素はDEPT表のBEANにSELECTして値を取ってくるカラムをString型で表記します。この第四要素は省略可能で、省略するとすべてのレコード値がBEANにセットされます。SQL文で書けば SELECT * FROM DEPT ということです。
関係式の意味をもう少し詳しく説明すると、
EMPのBEANには、スコットさんの従業員IDである7788となっていて、
所属は10です。この値は部門IDであり、DEPT表のプライマリーキーでもあります。
addJoin関数で結合関係を定義するということは、DEPT表をSELECTするSQL文は以下のようになります。
SELECT * FROM DEPT WHERE DEPTNO=10;
以下のSQL文が発行されるわけではありません。
SELECT * FROM EMP,DEPT WHERE EMP.DEPTNO=DEPT.DEPTNO;
以下のテストケースでは、さらにEMP表とも関係させています。
EMP表とEMP表の関係は、所属がスコットさんと同じで、かつ、同じ仕事をしている従業員をSELECTします。
スコットさんのJOBはANALYSTですね。
addJoin関数の第二引数以降はListの可変引数ですので、複数の関係条件を指定することができます。
beanobj.bean.setEMPNO(7788) beanobj.addJoin("scott.bean.DEPT",List("DEPTNO","=","DEPTNO","DEPTNO,DNAME,LOC")) beanobj.addJoin("scott.bean.EMP",List("DEPTNO","=","DEPTNO"),List("JOB","=","JOB")) val r = beanobj.find println(r.toString) val s = beanobj.getjoinEntity("scott.bean.DEPT") match { case Some(v) => v.foreach(w => println(" DEPT " + w.toString)) case None => Nil } val s3 = beanobj.getjoinEntity("scott.bean.EMP") match { case Some(v) => v.foreach(w => println(" EMP " + w.toString)) case None => Nil }
結果です
所属はRESEARCH部門。部門の所在地はDALLASです。
フォードさんが同じ所属で仕事がANALYSTです。スコットさんもSELECTされます。
--- Start TSET --- getClass=class seedo.database.BeanTalk JDBC Driver:MySQL-AB JDBC Driver Version:mysql-connector-java-5.1.8 ( Revision: ${svn.Revision} ) Database:MySQL Version:5.5 --- TSET 3 --- 7788,SCOTT,ANALYST,7566,1987-04-19 00:00:00.0,3000,,20 DEPT 20,RESEARCH,DALLAS EMP 7788,SCOTT,ANALYST,7566,1987-04-19 00:00:00.0,3000,,20 EMP 7902,FORD,ANALYST,7566,1981-12-03 00:00:00.0,3000,,20 --- End TEST ---
次は、JOIN条件を外します
removeAllJoin関数を呼び出すと、EMPとDEPTの1:Nと、EMPとEMPの1:Nの関係が削除されます。
beanobj.removeAllJoin beanobj.bean.setEMPNO(7782) val r3 = beanobj.find
結果は、7782番のクラークさんがSELECTされますが、
先ほどの関係はないので、DEPTとEMPのディテールはSELECTされません。
--- Start TSET --- getClass=class seedo.database.BeanTalk JDBC Driver:MySQL-AB JDBC Driver Version:mysql-connector-java-5.1.8 ( Revision: ${svn.Revision} ) Database:MySQL Version:5.5 --- TSET 4 --- 7782,CLARK,MANAGER,7839,1981-06-09 00:00:00.0,2450,,10 --- End TEST ---
次のテストケースでは、マスターがDEPT表、ディテールがEMP表です。
ここでは部門IDが10番に所属する従業員がSELECTされます。
データとしての意味はないですが、DEPT表とDEPT表も関係づけします。
beanobjDept.bean.setDEPTNO(10) beanobjDept.addJoin("scott.bean.EMP",List("DEPTNO","=","DEPTNO")) beanobjDept.addJoin("scott.bean.DEPT",List("DEPTNO","=","DEPTNO")) val r1 = beanobjDept.find
ACCOUNTING部門はニューヨークにあり、3人が所属していることがわかります。
--- Start TSET --- getClass=class seedo.database.BeanTalk JDBC Driver:MySQL-AB JDBC Driver Version:mysql-connector-java-5.1.8 ( Revision: ${svn.Revision} ) Database:MySQL Version:5.5 --- TSET 5 --- 10,ACCOUNTING,NEW YORK EMP 7782,CLARK,MANAGER,7839,1981-06-09 00:00:00.0,2450,,10 EMP 7839,KING,PRESIDENT,,1981-11-17 00:00:00.0,5000,,10 EMP 7934,MILLER,CLERK,7782,1982-01-23 00:00:00.0,1300,,10 DEPT 10,ACCOUNTING,NEW YORK --- End TEST ---
DEPT BEAN クラス
最後に、DEPTクラスです。
このクラスは、SeedoフレームワークにあるBeanクラスの自動生成ツールによって作成されたものです。
MySQLやOracleにアクセスしてディクショナリ情報を参照してテーブルやビューのBeanコードを生成します。
package scott.bean import scala.xml._ import java.math.BigDecimal import java.sql.{Blob,Date,Timestamp} import javax.persistence.{Column,Entity,Id,Table,Lob,UniqueConstraint,SequenceGenerator} import seedo.Util @Entity @Table(name="DEPT") class DEPT{ // Type = TABLE // Index = // Commect = - def PRIMARY_KEY :Array[String] = Array("DEPTNO") def timestamp_COLUMN :Array[String] = Array() def COLUMN :Array[String]= Array( "DEPTNO" // NUMBER(2) ,"DNAME" // VARCHAR2(14) ,"LOC" // VARCHAR2(13) ) def clear :Unit= { DEPTNO = null DNAME = null LOC = null for (i <- 0 to modifiedProperty.length - 1) { modifiedProperty(i) = false } } override def clone : DEPT = { var base = new DEPT if (DEPTNO != null) { base.DEPTNO = new BigDecimal(DEPTNO.toString) } if (DNAME != null) { base.DNAME = new String(DNAME) } if (LOC != null) { base.LOC = new String(LOC) } for (i <- 0 to modifiedProperty.length - 1) { base.modifiedProperty(i) = modifiedProperty(i) } base } def isModified : Boolean = { modifiedProperty.foreach (i => { if (i) { return true } }) return false } def isPrimaryKey(columnName:String) : Boolean = { if (columnName.equals("DEPTNO")) {return true} false } def isColumn(columnName:String) : Int= { return 0 } /** * Getter * @return the DEPTNO */ def getDEPTNO:BigDecimal = DEPTNO /** * Setter * @param Deptno the DEPTNO to set */ def setDEPTNO( Deptno:BigDecimal) : Unit = { this.DEPTNO = Deptno modifiedProperty(0) = true } /** * Setter * @param Deptno the DEPTNO to set */ def setDEPTNO(Deptno:Int) :Unit = { this.DEPTNO = new BigDecimal(Deptno) modifiedProperty(0) = true } /** * Setter * @param Deptno the DEPTNO to set */ def setDEPTNO(Deptno:Long) : Unit = { this.DEPTNO = new BigDecimal(Deptno) modifiedProperty(0) = true } /** * Setter * @param Deptno the DEPTNO to set */ def setDEPTNO(Deptno:Double) : Unit = { this.DEPTNO = new BigDecimal(String.valueOf(Deptno)) modifiedProperty(0) = true } /** * Getter * @return the DNAME */ def getDNAME:String = DNAME /** * Setter * @param Dname the DNAME to set */ def setDNAME( Dname:String) : Unit = { this.DNAME = Dname modifiedProperty(1) = true } /** * Getter * @return the LOC */ def getLOC:String = LOC /** * Setter * @param Loc the LOC to set */ def setLOC( Loc:String) : Unit = { this.LOC = Loc modifiedProperty(2) = true } override def toString :String= { var buf = new StringBuffer if(modifiedProperty(0)){ buf.append(if(DEPTNO!=null){DEPTNO}else{""}) } if(modifiedProperty(1)){ if(buf.length>0)buf.append(",") buf.append(if(DNAME!=null){DNAME}else{""}) } if(modifiedProperty(2)){ if(buf.length>0)buf.append(",") buf.append(if(LOC!=null){LOC}else{""}) } buf.toString } def toXml :Node = { <DEPT><DEPTNO>{DEPTNO}</DEPTNO><DNAME>{if(DNAME != null){seedo.Util.rpxml(DNAME)}else{""}}</DNAME><LOC>{if(LOC != null){seedo.Util.rpxml(LOC)}else{""}}</LOC></DEPT> } def toXmlModified :Node = { <DEPT>{if(modifiedProperty(0)){<DEPTNO>{DEPTNO}</DEPTNO>}}{if(modifiedProperty(1)){<DNAME>{if(DNAME != null){seedo.Util.rpxml(DNAME)}else{""}}</DNAME>}}{if(modifiedProperty(2)){<LOC>{if(LOC != null){seedo.Util.rpxml(LOC)}else{""}}</LOC>}}</DEPT> } // generate json format def toJson :String= { var buf = new StringBuffer buf.append("{") if(modifiedProperty(0)){ buf.append("\"DEPTNO\":"+(if(DEPTNO!=null){DEPTNO}else{"null"})) } if(modifiedProperty(1)){ if(buf.length>0)buf.append(",") buf.append("\"DNAME\":\""+(if(DNAME!=null){DNAME}else{"null"})+"\"") } if(modifiedProperty(2)){ if(buf.length>0)buf.append(",") buf.append("\"LOC\":\""+(if(LOC!=null){LOC}else{"null"})+"\"") } buf.append("}") buf.toString } override def hashCode :Int = { return (DEPTNO).hashCode } @Id @Column(name="DEPTNO",precision=2,nullable=false,unique=true,columnDefinition="NUMBER(2)") @SequenceGenerator(name="PK_DEPT", sequenceName="PK_DEPT") var DEPTNO:BigDecimal = null @Column(name="DNAME",length=14,nullable=true,unique=false,columnDefinition="VARCHAR2(14)") var DNAME:String = null @Column(name="LOC",length=13,nullable=true,unique=false,columnDefinition="VARCHAR2(13)") var LOC:String = null var modifiedProperty:Array[Boolean] = Array(false,false,false,false) }