xorm
xorm是一个Go语言ORM库. 通过它可以使数据库操作非常简便.
用法入门:
前提:定义本文中用到的struct和基本代码如下
// 银行账户
type Account struct {Id int64Name string `xorm:"unique"`Balance float64Version int `xorm:"version"` // 乐观锁
}
var x *xorm.Engine
创建orm引擎
注意:若想配合mysql,需要提前加载mysql驱动,通过如此方式
import _ "github.com/go-sql-driver/mysql"x,err:=xorm.NewEngine("mysql", "root:111111@/sys?charset=utf8")
自动同步表结构
if err = x.Sync2(new(Account)); err != nil {log.Fatalf("Fail to sync database: %v\n", err)}
Sync2会进行如下这些操作:
- 自动检测和创建表,这个检测是根据表的名字
- 自动检测和新增表中的字段,这个检测是根据字段名,同时对表中多余的字段给出警告信息
- 自动检测,创建和删除索引和唯一索引,这个检测是根据索引的一个或多个字段名,而不根据索引名称。因此这里需要注意,如果在一个有大量数据的表中引入新的索引,数据库可能需要一定的时间来建立索引。
- 自动转换varchar字段类型到text字段类型,自动警告其它字段类型在模型和数据库之间不一致的情况。
- 自动警告字段的默认值,是否为空信息在模型和数据库之间不匹配的情况
以上这些警告信息需要将engine.ShowWarn
设置为 true
才会显示。
增删改操作
增加操作:插入一条新的记录,该记录必须是未存在的,否则会返回错误:
_, 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的指针。
乐观锁
乐观锁是 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
使用 Cols 方法可以指定查询特定字段,当只有结构中的某个字段的值对您有价值时,就可以使用它:
x.Cols("name").Iterate(new(Account), printFn)var printFn = func(idx int, bean interface{}) error {//dosomethingreturn nil
}
此处,所查询出来的结构只有 Name 字段有值,其它字段均为零值。要注意的是,Cols 方法所接受的参数是数据表中对应的名称,而不是字段名称。
排除特定字段 --Omit
当您希望刻意忽略某个字段的查询结果时,可以使用 Omit 方法:
x.Omit("name").Iterate(new(Account), printFn)
此处,所查询出来的结构只有 Name 字段为零值。要注意的是,Omit 方法所接受的参数是数据表中对应的名称,而不是字段名称。
查询结果偏移 --Limit
查询结果偏移在分页应用中最为常见,通过 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)}
Demo:
package mainimport (
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/go-xorm/xorm"
"goShare/xorm_models/models"
"time"
)func main() {var engine *xorm.Engine
//连接数据库
engine, err := xorm.NewEngine("mysql", "root:112233@tcp(127.0.0.1:3305)/test?charset=utf8")
if err != nil {fmt.Println(err)return
}
//连接测试
if err := engine.Ping(); err != nil {fmt.Println(err)return
}
defer engine.Close() //延迟关闭数据库
fmt.Println("数据库链接成功")//查询单条数据
var doc models.DoctorTb
b, _ := engine.Where("name = ?", "钟南山").Get(&doc)
if b {fmt.Println(doc)
} else {fmt.Println("数据不存在")
}//查询单条数据方式2 会根据结构体的
doc2 := models.DoctorTb{Name: "钟南山"}
b, _ = engine.Get(&doc2)
fmt.Println(doc2)//新增数据
doc3 := models.DoctorTb{0, "王医生", 48, 1, time.Now()}
i3, _ := engine.InsertOne(doc3)
fmt.Println("新增结果:", i3)//查询列表
docList := make([]models.DoctorTb, 0)
engine.Where("age > ? or name like ?", 40, "林%").Find(&docList)
fmt.Println("docList:", docList)//查询列表方式2
docList2 := make([]models.DoctorTb, 0)
engine.Where("age > ?", 40).Or("name like ?", "林%").OrderBy("Id desc").Find(&docList2)
fmt.Println("docList2:", docList2)//查询分页
docList3 := make([]models.DoctorTb, 0)
page := 0 //页索引
pageSize := 2 //每页数据
limit := pageSize
start := page * pageSize
totalCount, err := engine.Where("age > ? or name like ?", 40, "林%").Limit(limit, start).FindAndCount(&docList3)
fmt.Println("总记录数:", totalCount, "docList3:", docList3)//直接用语句查询
docList4 := make([]models.DoctorTb, 0)
engine.SQL("select * from doctor_tb where age > ?", 40).Find(&docList4)
fmt.Println("docList4:", docList4)//删除
docDel := models.DoctorTb{Name: "王医生"}
iDel, _ := engine.Delete(&docDel)
fmt.Println("删除结果:", iDel)//删除方式2
engine.Exec("delete from doctor_tb where Id = ?", 3)//更新数据
doc5 := models.DoctorTb{Name: "钟医生"}
//更新数据ID为2的记录名字更改为“钟医生”
iUpdate, _ := engine.Id(2).Update(&doc5)
fmt.Println("更新结果:", iUpdate)//指定表名查询.Table()
user := models.UserTb{Id: 2}
b, _ = engine.Table("user_tb").Get(&user)
fmt.Println(user)//事务
session := engine.NewSession()
defer session.Close()
err = session.Begin()
_, err = session.Exec("delete from doctor_tb where Id = ?", 6)
if err != nil {session.Rollback()return
}
_, err = session.Exec("delete from user_tb where Id = ?", 10)
if err != nil {session.Rollback()return
}
err = session.Commit()
if err != nil {return
}
fmt.Println("事务执行成功")
}
下面是一个简单的银行存取款的小例子
package mainimport ("errors""log""github.com/go-xorm/xorm"_ "github.com/mattn/go-sqlite3"
)// 银行账户
type Account struct {Id int64Name string `xorm:"unique"`Balance float64Version int `xorm:"version"` // 乐观锁
}// ORM 引擎
var x *xorm.Enginefunc 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 -= balancea2.Balance += balanceif _, 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 -= withdrawif _, 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
}
https://www.jianshu.com/p/13d46e5d2d2a