Scalaで永続化を試してみる。

Scalaでは、永続化に関してはSLICK(=ScalaQuery)を利用するべきなのかもしれないが、まずはベタでJDBCを利用してDBへのアクセスを試してみた。

(1) スキーマ定義とエンティティクラス

helloというデータベースにaccountというテーブルを定義してみた。このテーブル(account)に対応するエンティティクラス(Account)を作成してみる。


use hello; // MySQLの場合
// \connect hello; // Progresqlの場合
create table account (
uid varchar(10) primary key,
name varchar(20),
pwd varchar(32)
);
insert into account (uid,name,pwd) values ('ID00001', '管理者Aさん','aaa');
insert into account (uid,name,pwd) values ('ID00002', 'Bさん','bbb');
insert into account (uid,name,pwd) values ('ID00003', 'Cさん','ccc');
select * from account;
エンティティクラス(Account)は以下の感じだ。

package hello.domain

class Account(a_uid: String, a_name: String = null, a_pwd: String = null) {

var uid: String = a_uid
var name: String = a_name
var pwd: String = a_pwd

override def toString(): String = {
uid + "(" + name + ")"
}
def dump(): Unit = {
println("uid=" + uid)
println("name=" + name)
println("pwd=" + pwd)
}
}

初心者なので「(): Unit = 」などは省略しない。引数にvalやvarを指定して、属性の定義を割愛できるようだが、初心者はやるべきではないと思う。以下の感じでテストできる。

package hello
import hello.domain.Account

object Main {
def main(args:Array[String]) = {
var user = new Account("ID00001", "Aさん")
user.pwd = user.name + "のパスワード";
user.dump
println(user)
}
}

(2) DBへのアクセッサクラス

そして、DBにアクセスするために3つのクラスを定義してみた。初心者なので、まだJavaっぽくしか書けない。まず、DBからConnectionを取得するオブジェクト(PersistenceMgr)だ。一応、MySQLとProgresqlに接続できるように書いてみた。


package hello.db
import java.sql.{Connection, DriverManager};

object PersistenceMgr {

/**
* postgresqlmysqlに対応。都度、dbmsに所定の字面をセットすること
* propertiesなどから取得するようにはしたい。
* アカウントは、/に任意に設定。database名はhelloで固定
* encodingはUTF-8であること
*/
// val dbms: String = "postgresql"
val dbms: String = "mysql"
var conn: Connection = null

/**
* Connectionを取得する。1回生成したConnectionを使いまわす。
*/
def connect(): Connection = {
if (conn == null){
var connStr: String = null
dbms match {
case "mysql" =>
connStr = "jdbc:mysql://localhost/hello"
Class.forName("com.mysql.jdbc.Driver").newInstance()
case "postgresql" =>
connStr = "jdbc:progresql://localhost/hello"
Class.forName("org.postgresql.Driver").newInstance()
case _ =>
}
if (connStr != null) {
conn = DriverManager.getConnection(connStr, "", "")
}
}
conn
}
}

次に、DBへのアクセッサのベースクラス(PersistenceBase)だ。データベースの所在を掩蔽し、Connectionに対してアクセスする。

package hello.db
import java.sql.{Connection, Statement};

class PersistenceBase {

/**
* Connectionはコンストラクタ実行時に取得される。
*/
private val conn: Connection = PersistenceMgr.connect
private var stmt: Statement = null

/**
* Statementを取得する。
*/
def statement: Statement = {
close // まず、念のためstmtのクローズを試みる。
stmt = conn.createStatement // stmtを生成する。
stmt
}

/**
* Statementを明示的にクローズする。
*/
def close {
if (stmt != null){
stmt.close
stmt = null
}
}
}

最後にPersistenceBaseを継承して、テーブル(account)に対応するアクセッサクラス(AccountDA)を書いてみる。Connectionを掩蔽し、Statementに対してアクセスする。select,updateといったSQLのイメージをあえて残しておく。

package hello.db
import hello.domain._
import java.sql.{ResultSet}
import scala.collection.mutable._

class AccountDA extends PersistenceBase {

private def createAccount(rs: ResultSet): Account = {
new Account(rs.getString("uid"), rs.getString("name"), rs.getString("pwd"))
}

/**
* ユーザーを検索する。
*/
def selectUsers(): List[Account] = {
var arr = new ListBuffer[Account]
try {
val rs: ResultSet = statement.executeQuery("SELECT * FROM account ORDER BY uid")
while (rs.next) {
arr += createAccount(rs)
}
} finally {
close
}
arr.toList
}
/**
* UIDを指定してユーザーを取得する。
*/
def selectUser(uid: String): Account = {
var user: Account = null
try {
val rs: ResultSet = statement.executeQuery("SELECT * FROM account WHERE uid='" + uid + "'")
if (rs.next) {
user = createAccount(rs)
}
} finally {
close
}
user
}
/**
* ユーザー情報を更新する。現在、名前のみ更新できる。
* 更新した件数を返す。通常は1件。
*/
def updateUser(user: Account): Int = {
var res = 0;
try {
res = statement.executeUpdate(
"UPDATE account SET name='" + user.name + "' WHERE uid='" + user.uid + "'"
);
} finally {
close
}
res
}
}

(3) アカウントを扱うコントローラクラス

永続化の部分はできたので、アカウントを扱うコントローラ(AccountMgr)を書いてみる。ここでDBアクセスを掩蔽する。


package hello.controller
import scala.collection.mutable._
import hello.domain._
import hello.db._

object AccountMgr {

/**
* DBアクセッサを保持しておく。
*/
private var ada: AccountDA = new AccountDA

/**
* 登録されているユーザー全員を取得する。
*/
def users(): List[Account] = {
ada.selectUsers
}

/**
* ユーザーを認証する。
* UIDとPWDで認証し、該当するアカウントがあればそれを生成して返す。
*/
def login(uid: String, pwd: String): Account = {
var user = ada.selectUser(uid)
if (user != null){
// 指定のUIDに該当するアカウントが存在する場合
if (pwd != user.pwd){
// パスワードが一致しない場合
user = null
}
}
user
}

/**
* ユーザー情報を更新する。現在は、名前のみ更新できる。
*/
def updateUser(user: Account): Int = {
ada.updateUser(user)
}

/**
* ユーザーリストをダンプする。
*/
def dumpUsers(v: List[Account]) {
var i = 1;
for (e <- v){
println("[" + i + "]=" + e)
i += 1
}
}
}

以上で、コントローラからDBへのアクセスも可能になった。ログイン認証なども実装できそうだ。以下の感じで動作を確認できる。

package hello
import hello.domain.Account
import hello.controller.AccountMgr

object Main {
def main(args:Array[String]) = {

val amgr = AccountMgr
var users = amgr.users // ユーザーリストを取得
amgr.dumpUsers(users)

var user = users.head // リスト先頭のユーザーを取得
user.name += "(更新)" // 名前を変更
amgr.updateUser(user) // 更新
amgr.dumpUsers(amgr.users) // ユーザーリストを再取得

user = amgr.login("ID00001","aaa") // 認証成功
println(user)
user = amgr.login("ID00001","xxx") // 認証失敗
println(user)
}
}

以上で一応、DBへのアクセスの手順も確認できた。次回は、ScalaらしくSLICKを使って練習してみよう。SLICKにもベタでSQLを記述するモードと、SQL掩蔽してくれるモードがあるようだ。

(おまけ) Eclipse上での型推論

Scalaでは型推論を利用して記述量を少なくできるが、Eclipse上では、型はできるだけ省略しない方がいいというジレンマがある。省略すると、Eclipseを操作しているときに型推論がちょくちょく動作するが、これが結構重い感じなのだ。例えば、


val amgr = AccountMgr
var users = amgr.users
users.head
だと、「amgr.」や「users.」と打ち込んだ時点で、型推論とメソッドや属性の一覧メニューの作成が始まるようだ。このタイミングで割り込まれると結構重く感じられる。

val amgr: AccountMgr = AccountMgr
var users: List[Account] = amgr.users
users.head
のように明示的に型を指定しておくと、結構すんなりいく感じなのだ。