使用 beedb 函式庫進行 ORM 開發

beego orm 是我開發的一個 Go 進行 ORM 操作的函式庫,它採用了 Go style 方式對資料庫進行操作,實現了 struct 到資料表記錄的對映。beego orm 是一個十分輕量級的 Go ORM 框架,開發這個函式庫的本意降低複雜的 ORM 學習曲線,儘可能在 ORM 的執行效率和功能之間尋求一個平衡,beego orm 是目前開源的 Go ORM 框架中實現比較完整的一個函式庫,而且執行效率相當不錯,功能也基本能滿足需求。

beego orm 是支援 database/sql 標準介面的 ORM 函式庫,所以理論上來說,只要資料庫驅動支援 database/sql 介面就可以無縫的接入 beego orm。目前我測試過的驅動包括下面幾個:

Mysql: github/go-mysql-driver/mysql

PostgreSQL: github.com/lib/pq

SQLite: github.com/mattn/go-sqlite3

Mysql: github.com/ziutek/mymysql/godrv

暫未支援資料庫:

MsSql: github.com/denisenkom/go-mssqldb

MS ADODB: github.com/mattn/go-adodb

Oracle: github.com/mattn/go-oci8

ODBC: bitbucket.org/miquella/mgodbc

安裝

beego orm 支援 go get 方式安裝,是完全按照 Go Style 的方式來實現的。

go get github.com/astaxie/beego

如何初始化

首先你需要 import 相應的資料庫驅動套件、database/sql 標準介面套件以及 beego orm 套件,如下所示:

import (
    "database/sql"
    "github.com/astaxie/beego/orm"
    _ "github.com/go-sql-driver/mysql"
)

func init() {
    //註冊驅動
    orm.RegisterDriver("mysql", orm.DR_MySQL)
    //設定預設資料庫
    orm.RegisterDataBase("default", "mysql", "root:root@/my_db?charset=utf8", 30)
    //註冊定義的 model
        orm.RegisterModel(new(User))

       // 建立 table
        orm.RunSyncdb("default", false, true)
}

PostgreSQL 配置:

//匯入驅動
// _ "github.com/lib/pq"

// 註冊驅動
orm.RegisterDriver("postgres", orm.DR_Postgres)

// 設定預設資料庫
//PostgresQL 使用者:postgres ,密碼:zxxx , 資料庫名稱:test , 資料庫別名:default
orm.RegisterDataBase("default", "postgres", "user=postgres password=zxxx dbname=test host=127.0.0.1 port=5432 sslmode=disable")

MySQL 配置:

//匯入驅動
//_ "github.com/go-sql-driver/mysql"

//註冊驅動
orm.RegisterDriver("mysql", orm.DR_MySQL)

// 設定預設資料庫
//mysql 使用者:root ,密碼:zxxx , 資料庫名稱:test , 資料庫別名:default
 orm.RegisterDataBase("default", "mysql", "root:zxxx@/test?charset=utf8")

Sqlite 配置:

//匯入驅動
//_ "github.com/mattn/go-sqlite3"

//註冊驅動
orm.RegisterDriver("sqlite", orm.DR_Sqlite)

// 設定預設資料庫
//資料庫存放位置:./datas/test.db , 資料庫別名:default
orm.RegisterDataBase("default", "sqlite3", "./datas/test.db")

匯入必須的 package 之後,我們需要開啟到資料庫的連結,然後建立一個 beego orm 物件(以 MySQL 為例),如下所示 beego orm:

func main() {
        o := orm.NewOrm()
}

簡單範例:

package main

import (
    "fmt"
    "github.com/astaxie/beego/orm"
    _ "github.com/go-sql-driver/mysql" // 匯入資料庫驅動
)

// Model Struct
type User struct {
    Id   int
    Name string `orm:"size(100)"`
}

func init() {
    // 設定預設資料庫
    orm.RegisterDataBase("default", "mysql", "root:root@/my_db?charset=utf8", 30)

    // 註冊定義的 model
    orm.RegisterModel(new(User))
//RegisterModel 也可以同時註冊多個 model
//orm.RegisterModel(new(User), new(Profile), new(Post))

    // 建立 table
    orm.RunSyncdb("default", false, true)
}

func main() {
    o := orm.NewOrm()

    user := User{Name: "slene"}

    // 插入表
    id, err := o.Insert(&user)
    fmt.Printf("ID: %d, ERR: %v\n", id, err)

    // 更新表
    user.Name = "astaxie"
    num, err := o.Update(&user)
    fmt.Printf("NUM: %d, ERR: %v\n", num, err)

    // 讀取 one
    u := User{Id: user.Id}
    err = o.Read(&u)
    fmt.Printf("ERR: %v\n", err)

    // 刪除表
    num, err = o.Delete(&u)
    fmt.Printf("NUM: %d, ERR: %v\n", num, err)
}

SetMaxIdleConns

根據資料庫的別名,設定資料庫的最大空閒連線

orm.SetMaxIdleConns("default", 30)

SetMaxOpenConns

根據資料庫的別名,設定資料庫的最大資料庫連線 (go >= 1.2)

orm.SetMaxOpenConns("default", 30)

目前 beego orm 支援列印除錯,你可以透過如下的程式碼實現除錯

 orm.Debug = true

接下來我們的例子採用前面的資料庫表 User,現在我們建立相應的 struct

type Userinfo struct {
    Uid     int `PK` //如果表的主鍵不是 id,那麼需要加上 pk 註釋,明確的說這個欄位是主鍵
    Username    string
    Departname  string
    Created     time.Time
}

type User struct {
    Uid          int `PK` //如果表的主鍵不是 id,那麼需要加上 pk 註釋,明確的說這個欄位是主鍵
    Name        string
    Profile     *Profile   `orm:"rel(one)"` // OneToOne relation
    Post        []*Post `orm:"reverse(many)"` // 設定一對多的反向關係
}

type Profile struct {
    Id          int
    Age         int16
    User        *User   `orm:"reverse(one)"` // 設定一對一反向關係(可選)
}

type Post struct {
    Id    int
    Title string
    User  *User  `orm:"rel(fk)"`
    Tags  []*Tag `orm:"rel(m2m)"`    //設定一對多關係
}

type Tag struct {
    Id    int
    Name  string
    Posts []*Post `orm:"reverse(many)"`
}

func init() {
    // 需要在 init 中註冊定義的 model
    orm.RegisterModel(new(Userinfo),new(User), new(Profile), new(Tag))
}

注意一點,beego orm 針對駝峰命名會自動幫你轉化成下劃線欄位,例如你定義了 Struct 名字為UserInfo,那麼轉化成底層實現的時候是user_info,欄位命名也遵循該規則。

插入資料

下面的程式碼示範了如何插入一條記錄,可以看到我們操作的是 struct 物件,而不是原生的 sql 語句,最後透過呼叫 Insert 介面將資料儲存到資料庫。

o := orm.NewOrm()
var user User
user.Name = "zxxx"
user.Departname = "zxxx"

id, err := o.Insert(&user)
if err == nil {
    fmt.Println(id)
}

我們看到插入之後user.Uid就是插入成功之後的自增 ID。

同時插入多個物件:InsertMulti

類似 sql 語句

insert into table (name, age) values("slene", 28),("astaxie", 30),("unknown", 20)

第一個參數 bulk 為並列插入的數量,第二個為物件的 slice

回傳值為成功插入的數量

users := []User{
    {Name: "slene"},
    {Name: "astaxie"},
    {Name: "unknown"},
    ...
}
successNums, err := o.InsertMulti(100, users)

bulk 為 1 時,將會順序插入 slice 中的資料

更新資料

繼續上面的例子來示範更新操作,現在 user 的主鍵已經有值了,此時呼叫 Insert 介面,beego orm 內部會自動呼叫 update 以進行資料的更新而非插入操作。

o := orm.NewOrm()
user := User{Uid: 1}
if o.Read(&user) == nil {
    user.Name = "MyName"
    if num, err := o.Update(&user); err == nil {
        fmt.Println(num)
    }
}

Update 預設更新所有的欄位,可以更新指定的欄位:

// 只更新 Name
o.Update(&user, "Name")
// 指定多個欄位
// o.Update(&user, "Field1", "Field2", ...)

//Where:用來設定條件,支援多個參數,第一個參數如果為整數,相當於呼叫了 Where("主鍵=?",值)。

查詢資料

beego orm 的查詢介面比較靈活,具體使用請看下面的例子

例子 1,根據主鍵取得資料:

o := orm.NewOrm()
var user User

user := User{Id: 1}

err = o.Read(&user)

if err == orm.ErrNoRows {
    fmt.Println("查詢不到")
} else if err == orm.ErrMissPK {
    fmt.Println("找不到主鍵")
} else {
    fmt.Println(user.Id, user.Name)
}

例子 2:

o := orm.NewOrm()
var user User

qs := o.QueryTable(user) // 回傳 QuerySeter
qs.Filter("id", 1) // WHERE id = 1
qs.Filter("profile__age", 18) // WHERE profile.age = 18

例子 3,WHERE IN 查詢條件:

qs.Filter("profile__age__in", 18, 20)
// WHERE profile.age IN (18, 20)

例子 4,更加複雜的條件:

qs.Filter("profile__age__in", 18, 20).Exclude("profile__lt", 1000)
// WHERE profile.age IN (18, 20) AND NOT profile_id < 1000

可以透過如下介面取得多條資料,請看範例

例子 1,根據條件 age>17,取得 20 位置開始的 10 條資料的資料

var allusers []User
qs.Filter("profile__age__gt", 17)
// WHERE profile.age > 17

例子 2,limit 預設從 10 開始,取得 10 條資料

qs.Limit(10, 20)
// LIMIT 10 OFFSET 20 注意跟 SQL 反過來的

刪除資料

beedb 提供了豐富的刪除資料介面,請看下面的例子

例子 1,刪除一筆資料

o := orm.NewOrm()
if num, err := o.Delete(&User{Id: 1}); err == nil {
    fmt.Println(num)
}

Delete 操作會對反向關係進行操作,此例中 Post 擁有一個到 User 的外來鍵。刪除 User 的時候。如果 on_delete 設定為預設的級聯操作,將刪除對應的 Post

關聯查詢

有些應用卻需要用到連線查詢,所以現在 beego orm 提供了一個簡陋的實現方案:

type Post struct {
    Id    int    `orm:"auto"`
    Title string `orm:"size(100)"`
    User  *User  `orm:"rel(fk)"`
}

var posts []*Post
qs := o.QueryTable("post")
num, err := qs.Filter("User__Name", "slene").All(&posts)

上面程式碼中我們看到了一個 struct 關聯查詢

Group By 和 Having

針對有些應用需要用到 group by 的功能,beego orm 也提供了一個簡陋的實現

qs.OrderBy("id", "-profile__age")
// ORDER BY id ASC, profile.age DESC

qs.OrderBy("-profile__age", "profile")
// ORDER BY profile.age DESC, profile_id ASC

上面的程式碼中出現了兩個新介面函式

GroupBy:用來指定進行 groupby 的欄位

Having:用來指定 having 執行的時候的條件

使用原生 sql

簡單範例:

o := orm.NewOrm()
var r orm.RawSeter
r = o.Raw("UPDATE user SET name = ? WHERE name = ?", "testing", "slene")

複雜原生 sql 使用:

func (m *User) Query(name string) user []User {
    var o orm.Ormer
    var rs orm.RawSeter
    o = orm.NewOrm()
    rs = o.Raw("SELECT * FROM user "+
        "WHERE name=? AND uid>10 "+
        "ORDER BY uid DESC "+
        "LIMIT 100", name)
    //var user []User
    num, err := rs.QueryRows(&user)
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Println(num)
        //return user
    }
    return
}

更多說明,請到beego.me

進一步的發展

目前 beego orm 已經獲得了很多來自國內外使用者的反饋,我目前也正在考慮支援更多資料庫,接下來會在更多方面進行改進

Last updated