DB Flavor

author: NISHIMOTO Keisuke
version: 0.0.0
date: 2009.03.02

1 Abstract

JDBCを軽くラップし、Scalaで使いやすくしたシンプルなO/Rマッパです。

以下の特徴を持ちます。

2 Specification

2.1 動作環境

以下の環境で動作します。

OS Javaが動作するOS(Windows/Linux/Mac OS Xなど)
Database JDBC 2.0およびSQL92に準拠したデータベース
Java Java 5(1.5)以上のランタイム
Scala Scala 2.7.2以上のランタイム

注意:

2.2 モデルの定義

モデルとは、SELECT(検索)/INSERT(挿入)/UPDATE(更新)/DELETE(削除)されるレコードとしてマッピングされるオブジェクトであり、検索・更新などを使います。

1レコードはモデルのインスタンスと等価で、複数レコードは、Seq(シーケンス)のオブジェクトとして抽象化され扱われます。Seqオブジェクトの定義はDB Flavorが用意しますので必要ありません。

通常関数型言語プログラミングにおいて、なるべく副作用が起こらないようimmutable(不変)のコレクションなどを使いますが、DB Flavorではプログラミングをしやすくすることを目的としているため、mutable(可変)のモデルとして定義します。

2.2.1 Scalaの場合

Scalaの場合は、プロパティメソッドを定義します。プロパティメソッドがそのままデータベーステーブルのフィールドにマッピングされます。

明示的にマッピングする場合は、メソッドに@DBFieldアノテーションをつけます。

クラス名はそのままテーブル名として扱われます。

class Person {
  @DBField var name: String = ""
  @DBField var address: String = ""
  @DBField var phone: String = ""
  // 確認用
  override def toString =
    "Person(name=" + name + ",address=" + address + ",phone=" + phone + ")"
}

2.2.2 Javaの場合 (未実装)

Javaの場合は、Java Beans規則に沿ったプロパティメソッドを定義します。プロパティメソッドがそのままデータベーステーブルのフィールドにマッピングされます。

明示的にマッピングする場合は、メソッドに@DBFieldアノテーションをつけます。

クラス名はそのままテーブル名として扱われます。

public class Person {
  String name = "";
  String address = "";
  String phone = "";

  @DBField
  public String setName(String value) {
    name = value;
  }
  @DBField
  public String getName() {
    return name;
  }

  @DBField
  public String setAddress(String value) {
    address = value;
  }
  @DBField
  public String getAddress() {
    return address;
  }

  @DBField
  public String setName(String name) {
    this.name = name;
  }
  @DBField
  public String getName() {
    return name;
  }

  // 確認用
  @Override
  public String toString() {
    return "Person(name=" + name + ",address=" + address + ",phone=" + phone + ")";
  }
}

2.3 検索

2.3.1 単一テーブルの検索 (未実装)

import jp.ne.cappuccino.keisuken.db.flavor._

// モデルクラス(2.2参照)
class Person {
  ...
}

val PERSON = classOf[Person]

// データベースオブジェクト作成
val url = "..."
val db = new DBFlavor(url)

// 検索と結果表示
//   SELECT * FROM PERSON
for (person <- db.find(PERSON))
  println(person)

// データベースクローズ
db.close

2.4 追加

2.4.1 単一テーブルへの追加 (未実装)

// モデルクラス(2.2参照)
class Person {
  ...
}

val db = new DBFlavor(url)
val person = new Person(
  "NISHIMOTO Keisuke", "Japan, Okayama, ...", ...)

db.append(person)

db.close

2.5 更新

2.5.1 単一テーブルの更新 (未実装)

class Person {
  ...
}

val db = new DBFlavor(url)
val person = new Person
person.name = "Foo"

// 全フィールド更新
db.update(
  person,
  or("name = ?", "Foo")
)
db.update(
  person,
  List('name),
  or("name = ?", "NISHIMOTO Keisuke")
)

db.close

2.6 削除

2.6.1 単一テーブルの削除 (未実装)

class Person {
  ...
}

val db = new DBFlavor(url)
val person = new Person
person.name = "Foo"

db.remove("name = ?" -> "NISHIMOTO Keisuke")

db.close

2.7 条件式 (未実装)

val PERSON = classOf[Person]

// 条件が1つの場合
//   DELETE FROM PERSON WHERE name = 'NISHIMOTO Keisuke'
db.remove(
  PERSON,
  "name = ?", "NISHIMOTO Keisuke"
)

// 条件が複数ですべてANDの場合
//   DELETE FROM PERSON WHERE name = 'NISHIMOTO Keiuske' and name = 'Foo' and 'Bar'
db.remove(
  PERSON,
  and("name = ?" -> List("NISHIMOTO Keisuke", "Foo", "Bar")
)

// 条件が複数でANDとORを組み合わせる場合
//   DELETE FROM PERSON
//   WHERE
//   (name = 'Foo' or name = 'Boo') and (address = 'Okayama' or address = 'Japan')
val values = List(
  "name" -> List("Foo", "Boo"),
  "address" -> List("Okayama", "Japan")
)
db.remove(
  PERSON,
  and(values.map {(name, values) =>
    or(name + " = ?", values)
  })
)

2.8 より複雑な操作

DB Flavorはデータベースの複雑な操作をサポートしません。しかし実際のデータベースを使ったアプリケーションでは、複雑な検索や挿入などをしたい場合があります。

この場合は、DB FlavorではSQLを書くことをお勧めします。

たとえば、次のようなサブクエリ場合、

SELECT COUNT(ID) FROM (
  SELECT ID FOO WHERE NAME LIKE ' 
)

2.8 事前チェック

実行前に実行SQL文を得ることができます。

val PERSON = classOf[Person]
val statement = db.removeStatement(PERSON, "name = ?", "NISHIMOTO Keisuke")
println(statement)
// DELETE FROM PERSON WHERE name = 'NISIHMOTO Keisuke'

Appendix

A1 API Reference

API Reference

A1.1 データベース操作クラス

A1.2 Traits

Statement

構文を示す型です。

// ???必要か???
trait Statement {
  def children: Condition
  def +=(child: Condition)
  def statement: String
  def statement(buf: StringBuffer): StringBuffer
}
Condition

条件式を示す型です。

// 条件式
trait Condition {
  def children: Seq[Condition]
  def statement: Stirng
  def statement(buf: StringBuffer): StringBuffer
}
// 条件式テンプレート
class TemplateCondition(val template, val value: Any) {
  def children: Seq[Condition]
  def statement: String = DBUtils.????(template, value)
  def statement(buf: StringBuffer): StringBuffer = {
    buf.append(statement)
    buf
  }
}

A2 Statement

andメソッド

AND条件を記述するためのテンプレートメソッド

def and(template: String, values: Any*): Condition
def andSeq(template: String, values: Seq[Any]): Condition
def andSeq(conds: Seq[(String,Any)]: Condition

// =
def andEq(name: String, values: Any*)
def andEqSeq(name: String, values: Seq[Any])
// !=
def andNq
// <
def andLt
// <=
def andLe
// >
def andGt
// >=
def andGe
// between ? and ?
def andBetween
val db = new DBFlavor(...)
val PERSON = classOf[Person]
db.remove(PERSON,
  and("name = ?", "Foo", "Boo", Bar")
)
...
val db = new DBFlavor(...)
val PERSON = classOf[Person]
val values = Liist("Foo", "Boo", "Bar")
db.remove(PERSON,
  andSeq("name = ?", values)
)
...
val db = new DBFlavor(...)
val PERSON = classOf[Person]
val values = List("name = ?" -> "Foo", "address = ?" -> "Japan")
db.remove(PERSON,
  andSeq(values)
)
...
db.remove(PERSON,
  andEq("name", "Foo", "Boo", "Bar")
)
// WHERE
//      (name = 'Foo' and address = 'Japan')
//   or (name = 'Boo' and address = 'Okayama')
val names = List('name, 'address)
val values = List(
  List("Foo", "Japan"),
  List("Boo", "Okayama")
)
db.remove(PERSON,
  orSeq(values.map {(name, address) ->
    ("name = ? and address = ?", name, address)
  })
)
db.remove(PERSON,
  orSeq(values, {andSeq(names, _)})
)

orメソッド

OR条件を記述するためのテンプレートメソッド

def or(conds: (String,Any)*): Condition
def or(conds: Seq[(String,Any)]: Condition
val db = new DBFlavor(...)
val PERSON = classOf[Person]
db.remove(PERSON,
  or("name = ?", "Foo", "Boo", Bar")
)
...

A3 ToDo

A2 ChangeLog