xorm
xorm是一個(gè)Go語言O(shè)RM庫. 通過它可以使數(shù)據(jù)庫操作非常簡便.
全部文檔點(diǎn)我
用法入門:
前提:定義本文中用到的struct和基本代碼如下
// 銀行賬戶
type Account struct {
Id int64
Name string `xorm:"unique"`
Balance float64
Version int `xorm:"version"` // 樂觀鎖
}
var x *xorm.Engine
- 創(chuàng)建orm引擎
注意:若想配合mysql福扬,需要提前加載mysql驅(qū)動(dòng)腕铸,通過如此方式
import _ "github.com/go-sql-driver/mysql"
x,err:=xorm.NewEngine("mysql", "root:111111@/sys?charset=utf8")
- 自動(dòng)同步表結(jié)構(gòu)
if err = x.Sync2(new(Account)); err != nil {
log.Fatalf("Fail to sync database: %v\n", err)
}
Sync2會(huì)進(jìn)行如下這些操作:
- 自動(dòng)檢測和創(chuàng)建表,這個(gè)檢測是根據(jù)表的名字
- 自動(dòng)檢測和新增表中的字段铛碑,這個(gè)檢測是根據(jù)字段名狠裹,同時(shí)對表中多余的字段給出警告信息
- 自動(dòng)檢測,創(chuàng)建和刪除索引和唯一索引汽烦,這個(gè)檢測是根據(jù)索引的一個(gè)或多個(gè)字段名涛菠,而不根據(jù)索引名稱。因此這里需要注意撇吞,如果在一個(gè)有大量數(shù)據(jù)的表中引入新的索引俗冻,數(shù)據(jù)庫可能需要一定的時(shí)間來建立索引。
- 自動(dòng)轉(zhuǎn)換varchar字段類型到text字段類型梢夯,自動(dòng)警告其它字段類型在模型和數(shù)據(jù)庫之間不一致的情況言疗。
- 自動(dòng)警告字段的默認(rèn)值,是否為空信息在模型和數(shù)據(jù)庫之間不匹配的情況
以上這些警告信息需要將engine.ShowWarn
設(shè)置為 true
才會(huì)顯示颂砸。
- 增刪改操作
**增加操作:插入一條新的記錄噪奄,該記錄必須是未存在的死姚,否則會(huì)返回錯(cuò)誤:
**
_, err := x.Insert(&Account{Name: name, Balance: balance})
刪除操作:
_, err := x.Delete(&Account{Id: id})
方法 Delete 接受參數(shù)后,會(huì)自動(dòng)根據(jù)傳進(jìn)去的值進(jìn)行查找勤篮,然后刪除都毒。比如此處,我們指定了 Account 的 ID 字段碰缔,那么就會(huì)刪除 ID 字段值與我們所賦值相同的記錄账劲;如果您只對 Name 字段賦值,那么 xorm 就會(huì)去查找 Name 字段值匹配的記錄金抡。如果多個(gè)字段同時(shí)賦值瀑焦,則是多個(gè)條件同時(shí)滿足的記錄才會(huì)被刪除。
刪除操作針對的對象沒有限制梗肝,凡是按照條件查找到的榛瓮,都會(huì)被刪除(單個(gè)與批量刪除)。
獲取和修改記錄:想要修改的記錄必須是提前存在的巫击,所以修改前要先查詢所要修改的記錄
獲取記錄:
Get方法
查詢單條數(shù)據(jù)使用Get方法禀晓,在調(diào)用Get方法時(shí)需要傳入一個(gè)對應(yīng)結(jié)構(gòu)體的指針,同時(shí)結(jié)構(gòu)體中的非空field自動(dòng)成為查詢的條件和前面的方法條件組合在一起查詢坝锰。
a. 根據(jù)Id來獲得單條數(shù)據(jù):
a:=&Account{}
has, err := x.Id(id).Get(a)
b. 根據(jù)where獲取單條數(shù)據(jù)
a := new(Account)
has, err := x.Where("name=?", "adn").Get(a)
c. 根據(jù)Account結(jié)構(gòu)體中存在的非空數(shù)據(jù)來獲取單條數(shù)據(jù)
a := &Account{Id:1}
has, err := x.Get(a)
返回的結(jié)果為兩個(gè)參數(shù)粹懒,一個(gè)has(bool類型)為該條記錄是否存在,第二個(gè)參數(shù)err為是否有錯(cuò)誤顷级。不管err是否為nil凫乖,has都有可能為true或者false。
在獲取到記錄之后愕把,我們就需要進(jìn)行一些修改拣凹,然后更新到數(shù)據(jù)庫:
a.Balance += deposit
// 對已有記錄進(jìn)行更新
_, err = x.Update(a)
注意,Update接受的參數(shù)是指針
批量獲取信息
err = x.Desc("balance").Find(&as)
在這里恨豁,我們還調(diào)用了 Desc 方法對記錄按照存款數(shù)額將賬戶從大到小排序嚣镜。
Find方法的第一個(gè)參數(shù)為slice的指針或Map指針,即為查詢后返回的結(jié)果橘蜜,第二個(gè)參數(shù)可選菊匿,為查詢的條件struct的指針。
- 樂觀鎖
樂觀鎖是 xorm 提供的一個(gè)比較實(shí)用的功能计福,通過在 tag 中指定 version 來開啟它跌捆。開啟之后,每次對記錄進(jìn)行更新的時(shí)候象颖,該字段的值就會(huì)自動(dòng)遞增 1佩厚。如此一來,您就可以判斷是否有其它地方同時(shí)修改了該記錄说订,如果是抄瓦,則應(yīng)當(dāng)重新操作潮瓶,否則會(huì)出現(xiàn)錯(cuò)誤的數(shù)據(jù)(同時(shí)對一個(gè)帳號進(jìn)行取款操作卻只扣了一次的數(shù)額)。
事務(wù)及回滾
廢話不多說钙姊,直接上示例代碼:
// 創(chuàng)建 Session 對象
sess := x.NewSession()
defer sess.Close()
// 開啟事務(wù)
if err = sess.Begin(); err != nil {
return err
}
if _, err = sess.Update(a1); err != nil {
// 發(fā)生錯(cuò)誤時(shí)進(jìn)行回滾
sess.Rollback()
return err
}
// 完成事務(wù)
return sess.Commit()
統(tǒng)計(jì)記錄條數(shù)- Count方法
統(tǒng)計(jì)數(shù)據(jù)使用Count方法毯辅,Count方法的參數(shù)為struct的指針并且成為查詢條件。
a := new(Account)
//返回滿足id>1的Account的記錄條數(shù)
total, err := x.Where("id >?", 1).Count(a)
//返回Account所有記錄條數(shù)
total,err = x.Count(a)
Iterate方法
Iterate方法提供逐條執(zhí)行查詢到的記錄的方法煞额,他所能使用的條件和Find方法完全相同
err := x.Where("id > ?=)", 30).Iterate(new(Account), func(i int, bean interface{})error{
user := bean.(*Account)
//do somthing use i and user
})
我們主要來看迭代函數(shù)的聲明:它接受 2 個(gè)參數(shù)思恐,第一個(gè)是當(dāng)前記錄所對應(yīng)的索引(該索引和 ID 的值毫無關(guān)系,只是查詢后結(jié)果的索引)膊毁,第二個(gè)參數(shù)則是保存了相關(guān)類型的空接口胀莹,需要自行斷言,例如示例中使用 bean.(*Account) 因?yàn)槲覀冎啦樵兊慕Y(jié)構(gòu)是 Account婚温。
查詢特定字段
使用 Cols 方法可以指定查詢特定字段嗜逻,當(dāng)只有結(jié)構(gòu)中的某個(gè)字段的值對您有價(jià)值時(shí),就可以使用它:
x.Cols("name").Iterate(new(Account), printFn)
var printFn = func(idx int, bean interface{}) error {
//dosomething
return nil
}
此處缭召,所查詢出來的結(jié)構(gòu)只有 Name 字段有值,其它字段均為零值逆日。要注意的是嵌巷,Cols 方法所接受的參數(shù)是數(shù)據(jù)表中對應(yīng)的名稱,而不是字段名稱室抽。
排除特定字段
當(dāng)您希望刻意忽略某個(gè)字段的查詢結(jié)果時(shí)搪哪,可以使用 Omit 方法:
x.Omit("name").Iterate(new(Account), printFn)
此處,所查詢出來的結(jié)構(gòu)只有 Name 字段為零值坪圾。要注意的是晓折,Omit 方法所接受的參數(shù)是數(shù)據(jù)表中對應(yīng)的名稱,而不是字段名稱兽泄。
查詢結(jié)果偏移
查詢結(jié)果偏移在分頁應(yīng)用中最為常見漓概,通過 Limit 方法可以達(dá)到一樣的目的:
x.Limit(3, 2).Iterate(new(Account), printFn)
該方法最少接受 1 個(gè)參數(shù),第一個(gè)參數(shù)表示取出的最大記錄數(shù)病梢;如果傳入第二個(gè)參數(shù)胃珍,則表示對查詢結(jié)果進(jìn)行偏移。因此蜓陌,此處的查詢結(jié)果為偏移 2 個(gè)后觅彰,再最多取出 3 個(gè)記錄。
日志記錄
一般情況下钮热,使用x.ShowSQL = true
來開啟 xorm 最基本的日志功能填抬,所有 SQL 都會(huì)被打印到控制臺(tái),但如果您想要將日志保存到文件隧期,則可以在獲取到 ORM 引擎之后飒责,進(jìn)行如下操作:
f, err := os.Create("sql.log")
if err != nil {
log.Fatalf("Fail to create log file: %v\n", err)
return
}
x.Logger = xorm.NewSimpleLogger(f)
LRU 緩存
作為唯一支持 LRU 緩存的一款 ORM赘娄,如果不知道如何使用這個(gè)特性,那將是非常遺憾读拆。不過擅憔,想要使用它也并不困難,只需要在獲取到 ORM 引擎之后檐晕,進(jìn)行如下操作:
cacher := xorm.NewLRUCacher(xorm.NewMemoryStore(), 1000)
x.SetDefaultCacher(cacher)
這樣就算是使用最基本的緩存功能了暑诸。該功能還支持只緩存某些表或排除緩存某些表,詳情可以參見 文章首部的官方文檔辟灰。
事件鉤子
官方一共提供了 6 類 事件鉤子个榕,示例中只演示其中 2 種:BeforeInsert 和 AfterInsert。全部內(nèi)容查看文章首部官方文檔
它們的作用分別會(huì)在 進(jìn)行插入記錄之前 和 完成插入記錄之后 被調(diào)用:
func (a *Account) BeforeInsert() {
log.Printf("before insert: %s", a.Name)
}
func (a *Account) AfterInsert() {
log.Printf("after insert: %s", a.Name)
}
下面是一個(gè)簡單的銀行存取款的小例子
package main
import (
"errors"
"log"
"github.com/go-xorm/xorm"
_ "github.com/mattn/go-sqlite3"
)
// 銀行賬戶
type Account struct {
Id int64
Name string `xorm:"unique"`
Balance float64
Version int `xorm:"version"` // 樂觀鎖
}
// ORM 引擎
var x *xorm.Engine
func init() {
// 創(chuàng)建 ORM 引擎與數(shù)據(jù)庫
var err error
x, err = xorm.NewEngine("mysql", "root:111111@/sys?charset=utf8")
if err != nil {
log.Fatalf("Fail to create engine: %v\n", err)
}
// 同步結(jié)構(gòu)體與數(shù)據(jù)表
if err = x.Sync(new(Account)); err != nil {
log.Fatalf("Fail to sync database: %v\n", err)
}
}
// 創(chuàng)建新的賬戶
func newAccount(name string, balance float64) error {
// 對未存在記錄進(jìn)行插入
_, err := x.Insert(&Account{Name: name, Balance: balance})
return err
}
// 獲取賬戶信息
func getAccount(id int64) (*Account, error) {
a := &Account{}
// 直接操作 ID 的簡便方法
has, err := x.Id(id).Get(a)
// 判斷操作是否發(fā)生錯(cuò)誤或?qū)ο笫欠翊嬖? if err != nil {
return nil, err
} else if !has {
return nil, errors.New("Account does not exist")
}
return a, nil
}
// 用戶轉(zhuǎn)賬
func makeTransfer(id1, id2 int64, balance float64) error {
// 創(chuàng)建 Session 對象
sess := x.NewSession()
defer sess.Close()
// 啟動(dòng)事務(wù)
if err = sess.Begin(); err != nil {
return err
}
a1, err := getAccount(id1)
if err != nil {
return err
}
a2, err := getAccount(id2)
if err != nil {
return err
}
if a1.Balance < balance {
return errors.New("Not enough balance")
}
a1.Balance -= balance
a2.Balance += balance
if _, err = sess.Update(a1); err != nil {
// 發(fā)生錯(cuò)誤時(shí)進(jìn)行回滾
sess.Rollback()
return err
}
if _, err = sess.Update(a2); err != nil {
sess.Rollback()
return err
}
// 完成事務(wù)
return sess.Commit()
return nil
}
// 用戶存款
func makeDeposit(id int64, deposit float64) (*Account, error) {
a, err := getAccount(id)
if err != nil {
return nil, err
}
sess := x.NewSession()
defer sess.Close()
if err = sess.Begin(); err != nil {
return nil, err
}
a.Balance += deposit
// 對已有記錄進(jìn)行更新
if _, err = sess.Update(a); err != nil {
sess.Rollback()
return nil, err
}
return a, sess.Commit()
}
// 用戶取款
func makeWithdraw(id int64, withdraw float64) (*Account, error) {
a, err := getAccount(id)
if err != nil {
return nil, err
}
if a.Balance < withdraw {
return nil, errors.New("Not enough balance")
}
sess := x.NewSession()
defer sess.Close()
if _, err = sess.Begin(); err != nil {
return nil, err
}
a.Balance -= withdraw
if _, err = sess.Update(a); err != nil {
return nil, err
}
return a, sess.Commit()
}
// 按照 ID 正序排序返回所有賬戶
func getAccountsAscId() (as []Account, err error) {
// 使用 Find 方法批量獲取記錄
err = x.Find(&as)
return as, err
}
// 按照存款倒序排序返回所有賬戶
func getAccountsDescBalance() (as []Account, err error) {
// 使用 Desc 方法使結(jié)果呈倒序排序
err = x.Desc("balance").Find(&as)
return as, err
}
// 刪除賬戶
func deleteAccount(id int64) error {
// 通過 Delete 方法刪除記錄
_, err := x.Delete(&Account{Id: id})
return err
}
注:本文參考