ScalaQuery 筆記(二)

連結資料庫

在 ScalaQuery 中,所有的資料庫操作都必須通過 Session(也就是一種 JDBC Connection 物件的 Wrapper),這個 Session 可以由 Database 物件取得。

建立其取得 Session 的過程非常簡單:首先用 Database.forURL() 來取得相對應的 JDBC 資料庫連線,從下面的函式可以看出,我們採用 SQLite 的 JDBC Driver,並且開啟一個叫做 example1.db 的 SQLite 資料庫。

val database = Database.forURL(url = "jdbc:sqlite:example1.db", driver = "org.sqlite.JDBC")

有了資料庫物件之後,我們就可以建立我們的資料庫 Session 了,在這裡最簡單的方式,是使用 withSession 這個方法,他會自動建立一個新的 Session,並且在區塊中的程式碼結束後,自動清除 Session。如果你需要在這個程式碼區塊中取得 Session 物件的話,也可以透過 threadLocalSession 這個函式取得。

database withSession {
    import org.scalaquery.session.Database.threadLocalSession
    println ("Hello, I'm using this session:" + threadLocalSession)
    println ("What you want to do with database?")
}

當然,你也可以在連線的時候,直接指定這個 Session 的變數名稱:

import org.scalaquery.session.Session
database withSession { mySession: Session =>
    println ("Hey, I have created a new DB session called 'mySession':" + mySession)
    println ("What you want to do with database?")
}

整理一下後,完整的程式碼如下:

import org.scalaquery.session.Database

object Example1
{
    def main (args: Array[String]) {
        val database = Database.forURL(url = "jdbc:sqlite:example1.db", driver = "org.sqlite.JDBC")

        // 下面的區塊會建立新的 Session,讓你可以操作資料庫,並且在這個區塊結
        // 束的時候自動關閉 Session。你可以使用 threadLocalSession 來取得這個
        // Session 物件。
        database withSession {
            import org.scalaquery.session.Database.threadLocalSession
            println ("Hello, I'm using this session:" + threadLocalSession)
            println ("What you want to do with database?")
        }

        database withSession {
            import org.scalaquery.session.Database.threadLocalSession
            println ("Hello, I'm using another session:" + threadLocalSession)
            println ("What you want to do with database?")
        }

        // 或者你也可以自己命名 Session 物件的變數,但你需要先 import Session 形別
        import org.scalaquery.session.Session
        database withSession { mySession: Session =>
            println ("Hey, I have created a new DB session called 'mySession':" + mySession)
            println ("What you want to do with database?")
        }

        // 但是如果你只是建立 Sesion,可是什麼都沒做的話,ScalaQuery 會很聰明的
        // 忽略掉資料庫連線喲。
    }
}

試著使用 sbt run 執行一下,會出現像是下面的執結果:

Hello, I'm using this session:org.scalaquery.session.BaseSession@1a26079
What you want to do with database?
Hello, I'm using another session:org.scalaquery.session.BaseSession@16bace0
What you want to do with database?
Hey, I have created a new DB session called 'mySession':org.scalaquery.session.BaseSession@22a7e1
What you want to do with database?

值得注意的是,在這個例子中,雖然我們建立了 Database Session,但因為實際上並沒有存取資料庫,ScalaQuery 很聰明地避掉了所有不需要的資料庫連線建立過程。檢查一下你的程式專案目錄,也會發現 example1.db 這個 SQLite 資料庫檔案並沒有被建立。

接著,我們要看看怎麼樣定義及建立我們的兩個圖書館管理系統資料表。

建立資料表

定義資料表 Schema

在 ScalaQuery 中,要定義資料表的話,需要建立一個 org.scalaquery.ql.basic.BasicTable 的物件並且給定每一個欄位的資料型態以及該欄位在資料表中的資料型態。

在下面的程式碼中,我們定義了一個在資料庫中名為 BooksTable 的資料表,總共有五個欄位。

import org.scalaquery.ql.basic.BasicTable
val books = new BasicTable[(Int, String, String, Option[String], Option[String])]("BooksTable") {
    def sn     = column[Int]("bookSN")
    def title  = column[String]("bookTitle")
    def author = column[String]("bookAuthor")
    def translator = column[Option[String]]("bookTranslator")
    def isbn = column[Option[String]]("bookISBN")

    def * = sn ~ title ~ author ~ translator ~ isbn
}

需要注意的是,在定義資料表的時候,我們需要在 BasicTable 物件中定義一個名為 * 的方法,這個方法定義了資料表內的欄位順序,以及資料型別,而建立此方法的方式,是使用 ~ 這個運算子來將 column 物件串連起來。

要建立資料表內的欄位,需要使用 column 這個函式,並且指定該欄位的在 Scala 程式中的資料型態,ScalaQuery 會將其自動轉換為 SQL 資料庫內的資料型態。在 ScalaQuery 中,column 預設可以處理以下的 Scala 資料型別:

  • Scala
    • Boolean
    • Byte
    • Int
    • Long
    • Float
    • Double
    • String
  • Java SQL
    • java.sql.Blob
    • java.sql.Clob
    • java.sql.Date
    • java.sql.Time
    • java.sql.Timestamp

因此在這個例子中,我們建立了五個欄位,在資料表中的名稱分別為 bookSN、bookTitle、bookAuthor、bookTranslator 以及 bookISBN,資料型態除了 bookSN 外,其他的欄位在資料庫的型態為字串,其中只有 trasnlator 和 isbn 兩個欄位在資料表中可以為 NULL

以上面的 books 來說,相當於下面的 SQL DDL:

CREATE TABLE "BooksTable" (
    "bookSN" INTEGER NOT NULL,
    "bookTitle" VARCHAR(254) NOT NULL,
    "bookAuthor" VARCHAR(254) NOT NULL,
    "bookTranslator" VARCHAR(254),
    "bookISBN" VARCHAR(254)
);

你可以發現,在這個 DDL 中,除了我們指定為型態為 Option[String]bookTranslatorbookISBN 的兩個欄位外,其他的欄位 Scala 都幫我們加上了 NOT NULL 的限制條件。

但是先等等,既然 Scala 內建支援了 Singleton 物件,為什麼不直接用 Singleton 物件來代表我們的資料表定義呢?

稍微整理一下後,我們可以用下面的三個 Singleton 物件來定義我們圖書館管理系統的資料表 Schema。

object Books extends BasicTable[(Int, String, String, Option[String], Option[String])]("BooksTable") {
    def sn     = column[Int]("bookSN")
    def title  = column[String]("bookTitle")
    def author = column[String]("bookAuthor")
    def translator = column[Option[String]]("bookTranslator")
    def isbn = column[Option[String]]("bookISBN")

    def * = sn ~ title ~ author ~ translator ~ isbn
}

object Users extends BasicTable[(Int, String, Option[String], Option[String])]("UsersTable") {
    def id = column[Int]("userID")
    def name = column[String]("userName")
    def phone = column[Option[String]]("userPhone")
    def email = column[Option[String]]("userEmail")

    def * = id ~ name ~ phone ~ email
}

object LendLog extends BasicTable[(Int, Int, Timestamp, Option[Timestamp])]("LendLogTable") {
    def userID = column[Int]("userID")
    def bookSN = column[Int]("bookSN")
    def lendDate = column[Timestamp]("lendDate")
    def returnDate = column[Option[Timestamp]]("returnDate")

    def * = userID ~ bookSN ~ lendDate ~ returnDate
}

建立資料表

有了資料表的 Schema 定義後,接下來要如何實際依照 Schema 建立這三個資料表呢?如同之前所提到的,所有的資料庫操作必須發生在 Session 中,同時我們也需要使用 import org.scalaquery.ql.basic.BasicDriver.Implicit._ 來引入可以將我們的 BasicTable 物件轉換成 SQL DDL 的轉換函式。

在這之後,就可以用下面的程式碼在資料庫中建立資料表了。如同你所見的,我們可以一次建立單一的資料表,也可以用 ++ 這個運算子把所有的資料庫 DDL 串起來,再一次呼叫 create 方法來建立所有的資料表。

val database = Database.forURL(url = "jdbc:sqlite::example2.db", driver = "org.sqlite.JDBC")
database withSession {
   import org.scalaquery.session.Database.threadLocalSession
   import org.scalaquery.ql.basic.BasicDriver.Implicit._

   Books.ddl.create                    // 建立單一資料表
   (Users.ddl ++ LendLog.ddl).create   // 也可以一次建立兩個資料表
}

完整可執行的程式碼範例如下:

import org.scalaquery.session.Database
import org.scalaquery.session.Database.threadLocalSession
import org.scalaquery.ql.basic.BasicTable
import java.sql.Timestamp

object Books extends BasicTable[(Int, String, String, Option[String], Option[String])]("BooksTable") {
    def sn     = column[Int]("bookSN")
    def title  = column[String]("bookTitle")
    def author = column[String]("bookAuthor")
    def translator = column[Option[String]]("bookTranslator")
    def isbn = column[Option[String]]("bookISBN")

    def * = sn ~ title ~ author ~ translator ~ isbn
}

object Users extends BasicTable[(Int, String, Option[String], Option[String])]("UsersTable") {
    def id = column[Int]("userID")
    def name = column[String]("userName")
    def phone = column[Option[String]]("userPhone")
    def email = column[Option[String]]("userEmail")

    def * = id ~ name ~ phone ~ email
}

object LendLog extends BasicTable[(Int, Int, Timestamp, Option[Timestamp])]("LendLogTable") {
    def userID = column[Int]("userID")
    def bookSN = column[Int]("bookSN")
    def lendDate = column[Timestamp]("lendDate")
    def returnDate = column[Option[Timestamp]]("returnDate")

    def * = userID ~ bookSN ~ lendDate ~ returnDate
}

object Example2
{
    def main (args: Array[String]) {
        val database = Database.forURL(url = "jdbc:sqlite:example2.db", driver = "org.sqlite.JDBC")

        database withSession {
            import org.scalaquery.ql.basic.BasicDriver.Implicit._

            Books.ddl.create                    // 建立單一資料表
            (Users.ddl ++ LendLog.ddl).create   // 也可以一次建立兩個資料表
        }
    }
}

執行完這隻程式之後,你應該會在專案目錄下,看到 example2.db 這個 SQLite 的資料庫檔案。我們可以使用 sqlite3 這隻 SQLite 的 client 程式,來檢視我們是否有成功建立三個資料表,以及他們的 Schema。

brianhsu@NBGentoo ~/ScalaQuery/ScalaQueryTut $ sqlite3 example2.db 
SQLite version 3.7.5
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> .table
BooksTable    LendLogTable  UsersTable  
sqlite> .schema BooksTable
CREATE TABLE "BooksTable" ("bookSN" INTEGER NOT NULL,"bookTitle" VARCHAR(254) NOT NULL,"bookAuthor" VARCHAR(254) NOT NULL,"bookTranslator" VARCHAR(254),"bookISBN" VARCHAR(254));
sqlite> .schema LendLogTable
CREATE TABLE "LendLogTable" ("userID" INTEGER NOT NULL,"bookSN" INTEGER NOT NULL,"lendDate" TIMESTAMP NOT NULL,"returnDate" TIMESTAMP);
sqlite> .schema UsersTable
CREATE TABLE "UsersTable" ("userID" INTEGER NOT NULL,"userName" VARCHAR(254) NOT NULL,"userPhone" VARCHAR(254),"userEmail" VARCHAR(254));
sqlite> 

看來一切都很成功,下一次就來看看怎麼樣將資料插入資料庫吧!

回響