golang-xorm库快速学习

xorm

xorm是一个Go语言ORM库. 通过它可以使数据库操作非常简便.

全部文档点我

用法入门:

前提:定义本文中用到的struct和基本代码如下

// 银行账户
type Account struct {
    Id      int64
    Name    string `xorm:"unique"`
    Balance float64
    Version int `xorm:"version"` // 乐观锁
}
var x *xorm.Engine
  1. 创建orm引擎

注意:若想配合mysql,需要提前加载mysql驱动,通过如此方式

import _ "github.com/go-sql-driver/mysql"

x,err:=xorm.NewEngine("mysql", "root:111111@/sys?charset=utf8")

  1. 自动同步表结构
if err = x.Sync2(new(Account)); err != nil {
        log.Fatalf("Fail to sync database: %v\n", err)
    }

Sync2会进行如下这些操作:

  • 自动检测和创建表,这个检测是根据表的名字
  • 自动检测和新增表中的字段,这个检测是根据字段名,同时对表中多余的字段给出警告信息
  • 自动检测,创建和删除索引和唯一索引,这个检测是根据索引的一个或多个字段名,而不根据索引名称。因此这里需要注意,如果在一个有大量数据的表中引入新的索引,数据库可能需要一定的时间来建立索引。
  • 自动转换varchar字段类型到text字段类型,自动警告其它字段类型在模型和数据库之间不一致的情况。
  • 自动警告字段的默认值,是否为空信息在模型和数据库之间不匹配的情况

以上这些警告信息需要将engine.ShowWarn 设置为 true 才会显示。

  1. 增删改操作

**增加操作:插入一条新的记录,该记录必须是未存在的,否则会返回错误:
**
_, err := x.Insert(&Account{Name: name, Balance: balance})

删除操作:

_, err := x.Delete(&Account{Id: id})

方法 Delete 接受参数后,会自动根据传进去的值进行查找,然后删除。比如此处,我们指定了 Account 的 ID 字段,那么就会删除 ID 字段值与我们所赋值相同的记录;如果您只对 Name 字段赋值,那么 xorm 就会去查找 Name 字段值匹配的记录。如果多个字段同时赋值,则是多个条件同时满足的记录才会被删除。

删除操作针对的对象没有限制,凡是按照条件查找到的,都会被删除(单个与批量删除)。

获取和修改记录:想要修改的记录必须是提前存在的,所以修改前要先查询所要修改的记录

获取记录:

Get方法

查询单条数据使用Get方法,在调用Get方法时需要传入一个对应结构体的指针,同时结构体中的非空field自动成为查询的条件和前面的方法条件组合在一起查询。

a. 根据Id来获得单条数据:

a:=&Account{}
has, err := x.Id(id).Get(a)

b. 根据where获取单条数据

a := new(Account)
has, err := x.Where("name=?", "adn").Get(a)

c. 根据Account结构体中存在的非空数据来获取单条数据

a := &Account{Id:1}
has, err := x.Get(a)

返回的结果为两个参数,一个has(bool类型)为该条记录是否存在,第二个参数err为是否有错误。不管err是否为nil,has都有可能为true或者false。

在获取到记录之后,我们就需要进行一些修改,然后更新到数据库:

a.Balance += deposit
// 对已有记录进行更新
_, err = x.Update(a)

注意,Update接受的参数是指针

批量获取信息

err = x.Desc("balance").Find(&as)

在这里,我们还调用了 Desc 方法对记录按照存款数额将账户从大到小排序。

Find方法的第一个参数为slice的指针或Map指针,即为查询后返回的结果,第二个参数可选,为查询的条件struct的指针。

  1. 乐观锁

乐观锁是 xorm 提供的一个比较实用的功能,通过在 tag 中指定 version 来开启它。开启之后,每次对记录进行更新的时候,该字段的值就会自动递增 1。如此一来,您就可以判断是否有其它地方同时修改了该记录,如果是,则应当重新操作,否则会出现错误的数据(同时对一个帐号进行取款操作却只扣了一次的数额)。

事务及回滚

废话不多说,直接上示例代码:

// 创建 Session 对象
sess := x.NewSession()
defer sess.Close()
// 开启事务
if err = sess.Begin(); err != nil {
    return err
}

if _, err = sess.Update(a1); err != nil {
    // 发生错误时进行回滚
    sess.Rollback()
    return err
} 

// 完成事务
return sess.Commit()

统计记录条数- Count方法

统计数据使用Count方法,Count方法的参数为struct的指针并且成为查询条件。


a := new(Account)
//返回满足id>1的Account的记录条数
total, err := x.Where("id >?", 1).Count(a)
//返回Account所有记录条数
total,err = x.Count(a)

Iterate方法

Iterate方法提供逐条执行查询到的记录的方法,他所能使用的条件和Find方法完全相同

err := x.Where("id > ?=)", 30).Iterate(new(Account), func(i int, bean interface{})error{
    user := bean.(*Account)
    //do somthing use i and user
})

我们主要来看迭代函数的声明:它接受 2 个参数,第一个是当前记录所对应的索引(该索引和 ID 的值毫无关系,只是查询后结果的索引),第二个参数则是保存了相关类型的空接口,需要自行断言,例如示例中使用 bean.(*Account) 因为我们知道查询的结构是 Account。

查询特定字段

使用 Cols 方法可以指定查询特定字段,当只有结构中的某个字段的值对您有价值时,就可以使用它:

x.Cols("name").Iterate(new(Account), printFn)

var printFn = func(idx int, bean interface{}) error {
    //dosomething
    return nil
}

此处,所查询出来的结构只有 Name 字段有值,其它字段均为零值。要注意的是,Cols 方法所接受的参数是数据表中对应的名称,而不是字段名称。

排除特定字段

当您希望刻意忽略某个字段的查询结果时,可以使用 Omit 方法:

x.Omit("name").Iterate(new(Account), printFn)
此处,所查询出来的结构只有 Name 字段为零值。要注意的是,Omit 方法所接受的参数是数据表中对应的名称,而不是字段名称。

查询结果偏移

查询结果偏移在分页应用中最为常见,通过 Limit 方法可以达到一样的目的:

x.Limit(3, 2).Iterate(new(Account), printFn)

该方法最少接受 1 个参数,第一个参数表示取出的最大记录数;如果传入第二个参数,则表示对查询结果进行偏移。因此,此处的查询结果为偏移 2 个后,再最多取出 3 个记录。

日志记录

一般情况下,使用x.ShowSQL = true来开启 xorm 最基本的日志功能,所有 SQL 都会被打印到控制台,但如果您想要将日志保存到文件,则可以在获取到 ORM 引擎之后,进行如下操作:

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,如果不知道如何使用这个特性,那将是非常遗憾。不过,想要使用它也并不困难,只需要在获取到 ORM 引擎之后,进行如下操作:

cacher := xorm.NewLRUCacher(xorm.NewMemoryStore(), 1000)
x.SetDefaultCacher(cacher)

这样就算是使用最基本的缓存功能了。该功能还支持只缓存某些表或排除缓存某些表,详情可以参见 文章首部的官方文档。

事件钩子

官方一共提供了 6 类 事件钩子,示例中只演示其中 2 种:BeforeInsert 和 AfterInsert。全部内容查看文章首部官方文档

它们的作用分别会在 进行插入记录之前 和 完成插入记录之后 被调用:

func (a *Account) BeforeInsert() {
log.Printf("before insert: %s", a.Name)
}

func (a *Account) AfterInsert() {
log.Printf("after insert: %s", a.Name)
}

下面是一个简单的银行存取款的小例子

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() {
    // 创建 ORM 引擎与数据库
    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)
    }

    // 同步结构体与数据表
    if err = x.Sync(new(Account)); err != nil {
        log.Fatalf("Fail to sync database: %v\n", err)
    }
}

// 创建新的账户
func newAccount(name string, balance float64) error {
    // 对未存在记录进行插入
    _, 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)
    // 判断操作是否发生错误或对象是否存在
    if err != nil {
        return nil, err
    } else if !has {
        return nil, errors.New("Account does not exist")
    }
    return a, nil
}

// 用户转账
func makeTransfer(id1, id2 int64, balance float64) error {
    // 创建 Session 对象
    sess := x.NewSession()
    defer sess.Close()
    // 启动事务
    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 {
        // 发生错误时进行回滚
        sess.Rollback()
        return err
    }
    if _, err = sess.Update(a2); err != nil {
        sess.Rollback()
        return err
    }
    // 完成事务
    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
    // 对已有记录进行更新
    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 方法使结果呈倒序排序
    err = x.Desc("balance").Find(&as)
    return as, err
}

// 删除账户
func deleteAccount(id int64) error {
    // 通过 Delete 方法删除记录
    _, err := x.Delete(&Account{Id: id})
    return err
}

注:本文参考

  1. Go名库讲解
  2. 官方文档
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容