Scala+DBMS+Web スカラ座の夜

2011年2月6日

Lift2.2 + seedo = DB

Filed under: Oracle11g,Scala — admin @ 3:12 AM

Lift2.2 の中から seedo1.1 によってOracle11g に接続しました。

Liftの良さは、DBを意識しなくてもアプリケーションを作ることができるのですが、
DBを意識してアプリケーションを作りたいときもあるわけです。
LiftやRuby on Railsのように処理や画面を中心にアプリケーションを作るのではなく、
データベースを設計して、データベースを中心にアプリケーションを作るという主義です。

Liftを使ってきた人からは、「意味ないじゃん」といわれそうですが、、、

Lift2.2
Lift2.2のインストールは、

http://liftweb.net/downloadに行って、
Lift 2.2 release Tar or Zip. からZIPファイルをダウンロードします。

ZIPファイルを展開すると4つのフォルダがあります。
ここでは、lift_basicを使います。

lift_basicフォルダの中は、

コマンドラインで次のコマンドを実行します。

sbt update ~jetty-run

コマンドラインで次のように表示したら
[info] Started SelectChannelConnector@0.0.0.0:8080
[info] == jetty-run ==
[success] Successful.

ブラウザから http://localhost:8080 にアクセスして次の画面が表示したらインストールは成功。

seedo framewark
ここからが、seedo framewarkを使ってプログラムを作ります、

lift_basic/src/main/scala/code/snippet/ フォルダに、HelloDb.scala ファイルを作ります。


package code {
package snippet {
	import _root_.scala.xml.{NodeSeq, Text}
	import _root_.net.liftweb.util._
	import _root_.net.liftweb.common._
	import _root_.java.util.Date
	import code.lib._
	import Helpers._
	import scala.collection.immutable._
	import scala.xml._
	import seedo.database._

	class HelloDb {
		def recx(record:Array[Any]) :Node =  {
			var nodes  = Queue.empty[Node]
			record.foreach{(fx) => nodes += <TD>{fx}</TD>}
			return <TR>{nodes}</TR>
		}
		def tb = {
			val sql = new Db
			val re = sql.executeQuery("select * from emp order by ename")
			sql.close
			var nodes  = Queue.empty[Node]
			re.foreach{(rec) => nodes += recx(rec)}
			"#list *" #> <table>{nodes}</table>
	  }
	  def title(xhtml:NodeSeq) = Text("タイトルA")
	}
}
}

次に、lift_basic/src/main/webapp/ フォルダにある index.html ファイルを次のように書き換えます。
「span class=”lift:helloDb.tb”」によって先ほどのHelloDb クラスのtb関数が呼び出されます。


<!DOCTYPE html>
<html>
  <head>
    <meta content="text/html; charset=UTF-8" http-equiv="content-type" />
    <title>Home</title>
<SCRIPT>
</SCRIPT>
  </head>
  <body class="lift:content_id=main">
    <div id="main" class="lift:surround?with=default;at=content">
      <h2>Welcome to your project!</h2>
      <p>
	<span class="lift:helloWorld.howdy">
	  Welcome to your Lift app at <span id="time">Time goes here</span>
	</span>
<HR/>
	<span class="lift:helloDb.tb">
	  Get Emp Table <span id="list">EMP table list</span>
	</span>
      </p>
    </div>
  </body>
</html>

JARファイルは、lift_basic/lib_managed/scala_2.8.1/compile/フォルダに置きます。
OracleのJDBCドライバなら、
ojdbc6.jar
orai18n.jar

MySQLのJDBCドライバなら、
mysql-connector-java-5.1.8-bin.jar

seedo framewarkのJARは、
seedo-1.1.jar

env.propertiesファイルにDB接続の定義を記述します。
lift_basic/src/main/resources/フォルダにenv.propertiesファイルを置きます。


driver = oracle.jdbc.driver.OracleDriver
#driver = com.mysql.jdbc.Driver

#dsn = jdbc:mysql://192.168.0.100:3306/seedo?useUnicode=true&characterEncoding=UTF-8
dsn = jdbc:oracle:thin:@127.0.0.1:1521:ORCL
user = scott
password = tiger
connection-jndi = off

seedo framewarkには、データベースのコネクションプールを管理するクラスが定義されていますので、LiftのDBとは干渉することなく使うことができます。

Lift2.2の起動は、lift_basiceフォルダにて
以下のコマンドを実行します。

sbt ~jetty-run

ブラウザから次のURLをアクセスします。

http://127.0.0.1:8080/index

ブラウザ画面はこの通りです。

うまくいかない場合
sbt から jetty-run すると、

lift_basic/lib_managed/scala_2.8.1/compile/フォルダにあるjarファイルを
lift_basic/target/scala_2.8.1/webapp/WEB-INF/lib/フォルダにコピーし、

同様に、
lift_basic/src/main/webapp/ フォルダにある index.html ファイル
lift_basic/target/scala_2.8.1/webapp/フォルダにコピーします。

また、
lift_basic/target/scala_2.8.1/webapp/WEB-INF/classes/code/snippet/フォルダには、コンパイルされたHelloDb.classなどのclassファイルが置かれています。

オリジナルにあるファイルが、targetフォルダにコピーされないことがあるのでファイルの中身を見て確かめましょう。

もうひとつ、最初にLiftを起動し、サンプル画面を表示させ、

sbt update ~jetty-run

コマンドラインでEnterキーを押して、Jettyを終了した後は、

次のコマンドでJettyは起動します。

sbt ~jetty-run

update をコマンドイランに入れると、追加したJARファイルはなくなってしまい、インストールした状態に戻ります。
jetty-run したときに、エラーが表示されたときはJARファイルが無くなっています。

[info] Compiling main sources...
[error] C:\lift-lift_22_sbt-22a67aa\lift_basic\src\main\scala\code\snippet\Hello
Db.scala:11: not found: value seedo
[error]         import seedo.database._
[error]                ^
[error] one error found
[info] == compile ==

2010年11月30日

ScalaでBean Class

Filed under: Oracle11g,Scala — admin @ 8:10 PM

ScalaでもBeanクラスを作る意味があるのか?

データベースを操作するために、Beanクラスは便利です。
「ScalaでもBeanクラスを作る意味があるのか?」という課題です。

ScalaにはAnyRefがあり、ListやArrayはJavaより使い勝手はいいとなると、
コンテナとしてのBeanクラスをわざわざ作るかです。

SELECT文の結果データを格納するのであればListやArrayで十分かもしれません。
でも、INSERTしたりUPDATEに使うには、データベース表の型をみてプログラムを書かねばなりません。

Beanオブジェクトであればデータベースの型定義に基づいてクラスを作りますから、

よく考えられたBeanクラスであれば、プログラマは型をテキストか日付か数値くらいで厳密に見なくてもデータベース操作することができます。

具体的にBeanクラスを作ってみますと、

Oracle Databaseに以下のあるMASTER表を定義します。
この表定義そのものはあまり意味がありません。テスト用です。

CREATE TABLE MASTER (
	RENTALODRID    NUMBER PRIMARY KEY
	, CUSTOMERID   NUMBER(6)
	, SALESPRICE   NUMBER
	, DLVFEETAX    NUMBER
	, DLVFEE       NUMBER
	, RNTODRDATE   DATE
	, LASTUPDATE   TIMESTAMP
	, RATE         NUMBER(3,2)
	, RATE1        NUMBER(6,2)
	, RATE2        FLOAT
	, RATE3        INTEGER
	, OTHER        VARCHAR2(100)
);



OracleDBでの表定義は以下のようになります。

SQL> desc master
 名前                                      NULL?    型
 ----------------------------------------- -------- ----------------

 RENTALODRID                               NOT NULL NUMBER
 CUSTOMERID                                         NUMBER(6)
 SALESPRICE                                         NUMBER
 DLVFEETAX                                          NUMBER
 DLVFEE                                             NUMBER
 RNTODRDATE                                         DATE
 LASTUPDATE                                         TIMESTAMP(6)
 RATE                                               NUMBER(3,2)
 RATE1                                              NUMBER(6,2)
 RATE2                                              FLOAT(126)
 RATE3                                              NUMBER(38)
 OTHER                                              VARCHAR2(100)



Oracle JDBCドライバが返してくるJava型を参考にしながら、Master表のBeanクラスを定義します。

ScalaでBeanクラスを作るからには、Beanクラスを使う側は、Scalaの型やデータベースの型を厳密に意識しなくてもいいようにしたい訳です。

型でいろいろと記述するのが数値と日時で、これが厄介です。

データベースの数値型は、桁に制約を定義できます。
整数なのか実数なのか。

OracleJDBCドライバは、数値型はBigDecimalクラスで値を操作します。
BigDecimalクラスはScalaでもJavaでも普段使わないクラスです。

new BigDecimal(100) とか、new BigDecimal( “3.14” ) とは書きたくないです。
Scalaのプログラムでは、IntやDoubleクラスでコードは書きます。
このクラスで表現できないサイズの数字や、小数点をきっちり保持するときはBigDecimalクラスの出番です。

日付の扱いも、OracleのSQL文では、TO_CHARとTO_DATEはなくてはならないものです。
でも、この構文をSQL文に書くのは面倒な話。
MySQLとは互換性もないし。

日付型やタイムスタンプ型には、直接、String で日付を ”2010/11/30” として渡したいです。
いちいち、TO_DATE( ‘2010/11/30’ , ‘YYYY/MM/DD’ ) とは書きたくないですから。
JavaのDateクラスも日時を指定して生成するもの面倒ですし。

setterには、あらかじめ代表的な引数の型を受け取るメソッドを定義しておけば利用する側でいちいち書かなくてもいいということです。

package seedo

import java.math.BigDecimal
import java.sql.{Date,Timestamp}
import java.text.{DateFormat,SimpleDateFormat}

class MASTER {
	val TABLE = "MASTER";
	val PRIMARY_KEY = "RENTALODRID";
	val timestamp_COLUMN = "LASTUPDATE";

	var RENTALODRID:BigDecimal = null
	var CUSTOMERID:Integer  = null
	var SALESPRICE:BigDecimal = null
	var DLVFEETAX:BigDecimal = null
	var DLVFEE:BigDecimal = null
	var RNTODRDATE:java.sql.Date = null
	var LASTUPDATE:Timestamp = null
	var RATE:java.lang.Double = null
	var RATE1:Double = 0.0
	var RATE2:Float = 0.0F
	var RATE3:Int = 0
	var OTHER:String = null

	def setRENTALODRID(value:BigDecimal) = RENTALODRID = value
	def setCUSTOMERID(value:Integer ) = CUSTOMERID = value
	def setSALESPRICETAX(value:BigDecimal) = RENTALODRID = value
	def setSALESPRICE(value:BigDecimal) = RENTALODRID = value
	def setDLVFEETAX(value:BigDecimal) = RENTALODRID = value
	def setDLVFEE(value:BigDecimal) = RENTALODRID = value
	def setDLVFEE(value:Double) = RENTALODRID = new BigDecimal(value)
	def setDLVFEE(value:java.lang.Double) = RENTALODRID = new BigDecimal(value.toString)
	def setDLVFEE(value:Integer) = RENTALODRID = new BigDecimal(value.toString)
	def setRNTODRDATE(value:java.sql.Date) = RNTODRDATE = value
	def setRNTODRDATE(value:java.util.Date) = RNTODRDATE = new java.sql.Date(value.getTime)
	def setRNTODRDATE(value:String) = RNTODRDATE = new java.sql.Date(DateFormat.getDateInstance().parse(value).getTime)
	def setLASTUPDATE(value:Timestamp) = LASTUPDATE = value
	def setLASTUPDATE(value:String) = LASTUPDATE = new Timestamp(DateFormat.getDateInstance().parse(value).getTime)
	def setRATE(value:java.lang.Double) = RATE = value
	def setRATE1(value:Double) = RATE1 = value
	def setRATE2(value:Float) = RATE2 = value
	def setRATE3(value:Int) = RATE3 = value
	def setOTHER(value:AnyRef) = {
		println("setOTHER(value:AnyRef)")
		OTHER = value match {
		//		case x : Class[Int] => value.toString
			case x : Class[Date] => {
				println("setOTHER(value:AnyRef) Class[Date]")
				val f = new SimpleDateFormat("yyyy/MM/dd")
				f.format(value)
			}
			case x : Class[String] => value.asInstanceOf[String]
			case _ => {
				println("setOTHER(value:AnyRef) _")
				if(value.getClass == classOf[java.sql.Date]){
					new SimpleDateFormat( "yyyy/MM/dd" ).format(value)
				}else if(value.getClass == classOf[java.util.Date]){
					new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss.SSS" ).format(value)
				}else{
					value.toString
				}
			}
		}
	}
	def setOTHER(value:Int) = {
		OTHER = value.toString
		println("setOTHER(value:Int)")
	}
	def setOTHER(value:String) = {
		OTHER = value
		println("setOTHER(value:String)")
	}
	var modified:Boolean = true
	var modifiedProperty = new Array[Boolean](12)

	override def toString :String  = {
		var buf = "["
		buf += RENTALODRID
		buf += "," + CUSTOMERID
		buf += "," + SALESPRICE
		buf += "," + DLVFEETAX
		buf += "," + DLVFEE
		buf += "," + RNTODRDATE
		buf += "," + LASTUPDATE
		buf += "," + OTHER
		buf += "]"
		buf
	}
	override def hashCode:Int = RENTALODRID.hashCode
}

setterには、それぞれの引数の型にあったものが呼び出されます。

Scalaですから、setter引数にAnyRefを定義した場合の動きに興味があります。

def setOTHER(value:AnyRef) と def setOTHER(value:String) とがあった場合は、引数にStringクラスのものを渡すと後者が呼び出されます。

def setOTHER(value:AnyRef)のなかのクラスの判定をmatch/case で書いてみました。

OTHER = value match {
     case x : Class[Date] => new SimpleDateFormat("yyyy/MM/dd ").format(value)
     case x : Class[Integer] => value.asInstanceOf[Integer].toString
     case x : Class[String] => value.asInstanceOf[String]
     case _ =>  value.toString
}

引数に、java.util.Date の値でも、java.sql.Dateの値 で渡してみても、Class[Date] が実行されません。
case _ => になってしまいました。

それと、Class[java.sql.Date] やClass[java.util.Date] と書くと、エラーとなります。

Scalaでデータベースを操作する限り Java JDBC のクラスを使う訳です。

Dateクラスは、Javaの借用となるとこれは難しいところです。

Powered by WordPress