前言
说起Go语言web中常用的ORM(Object relational mapping),首当其冲的就是GORM和XORM,在之前的文章中介绍过GORM,一直以来,我也是使用GORM,因为其hook机制(Before/After Create/Save/Updaye/Delete/Find)给予了很大的便利,还有其内置的日志记录器,在对于Go转语言过来的新手来说是很友好的。
XORM,最吸引我的莫过于他自动化的读写分离。在一些高并发的场景下, 读写分离的重要性不言而喻。下面开始尝试一下。
创建引擎
所有操作均需要事先创建并配置 ORM 引擎才可以进行。XORM支持两种 ORM 引擎,即 Engine 引擎和 Engine Group 引擎。一个 Engine 引擎用于对单个数据库进行操作,一个 Engine Group 引擎用于对读写分离的数据库或者负载均衡的数据库进行操作。Engine 引擎和 EngineGroup 引擎的API基本相同,所有适用于 Engine 的API基本上都适用于 EngineGroup,并且可以比较容易的从 Engine 引擎迁移到 EngineGroup引擎。
单个ORM引擎,也称为Engine。一个APP可以同时存在多个Engine引擎,一个Engine一般只对应一个数据库。Engine通过调用xorm.NewEngine
生成,如:
package main
import (
_ "github.com/go-sql-driver/mysql"
"github.com/go-xorm/xorm"
)
var engine *xorm.Engine
func main() {
var err error
engine, err = xorm.NewEngine("mysql", "用户名:密码@/数据库?charset=utf8")
if err != nil {
panic(err)
}
}
一般情况下如果只操作一个数据库,只需要创建一个engine
即可。
engine可以通过engine.Close来手动关闭,但是一般情况下可以不用关闭,在程序退出时会自动关闭。
日志
日志是一个接口,通过设置日志,可以显示SQL,警告以及错误等,默认的显示级别为INFO。
engine.ShowSQL(true)
,则会在控制台打印出生成的SQL语句;engine.Logger().SetLevel(core.LOG_DEBUG)
,则会在控制台打印调试及以上的信息;
如果你希望将日志输出到文件中,可以这样做:
f, err := os.Create("log/sql.log")
if err != nil {
println(err.Error())
return
}
engine.SetLogger(xorm.NewSimpleLogger(f))
连接池
// 设置连接池中的最大闲置连接数。
engine.SetMaxIdleConns(10)
// 设置数据库的最大连接数量。
engine.SetMaxOpenConns(150)
// 设置连接的最大可复用时间。
engine.SetConnMaxLifetime(10 * time.Second)
名称映射规则
名称映射规则主要负责结构体名称到表名和结构体field到表字段的名称映射。由core.IMapper接口的实现者来管理,xorm内置了三种IMapper实现:
core.SnakeMapper
,core.SameMapper
和core.GonicMapper
。
- SnakeMapper 支持struct为驼峰式命名,表结构为下划线命名之间的转换,这个是默认的Maper;
- SameMapper 支持结构体名称和对应的表名称以及结构体field名称与对应的表字段名称相同的命名;
- GonicMapper 和SnakeMapper很类似,但是对于特定词支持更好,比如ID会翻译成id而不是i_d。
当前SnakeMapper为默认值,如果需要改变时,在engine创建完成后使用
如果有表名和结构体名不一样的情况,可以通过这个文档来进行操作。
创建表
创建/迁移表,使用的是
CreateTables
方法:
type Role struct {
Id int `xorm:"pk autoincr"`
Name string `xorm:"varchar(255) not null unique"`
Level string `xorm:"varchar(255) not null"`
}
engine.CreateTables(&Role{})
不过需要的注意的是,和GORM的迁移不同的是,他只是创建表,一旦这个表是存在的状态,你后续在struct中添加新的字段也是无用的。
不过XORM也提供了同步数据结构的方法。
同步能够部分智能的根据结构体的变动检测表结构的变动,并自动同步。目前有两个实现:
- Sync
Sync将进行如下的同步操作:
* 自动检测和创建表,这个检测是根据表的名字
* 自动检测和新增表中的字段,这个检测是根据字段名
* 自动检测和创建索引和唯一索引,这个检测是根据索引的一个或多个字段名,而不根据索引名称
调用方法如下:
err := engine.Sync(new(User), new(Group))
- Sync2
Sync2对Sync进行了改进,目前推荐使用Sync2。Sync2函数将进行如下的同步操作:
* 自动检测和创建表,这个检测是根据表的名字
* 自动检测和新增表中的字段,这个检测是根据字段名,同时对表中多余的字段给出警告信息
* 自动检测,创建和删除索引和唯一索引,这个检测是根据索引的一个或多个字段名,而不根据索引名称。因此这里需要注意,如果在一个有大量数据的表中引入新的索引,数据库可能需要一定的时间来建立索引。
* 自动转换varchar字段类型到text字段类型,自动警告其它字段类型在模型和数据库之间不一致的情况。
* 自动警告字段的默认值,是否为空信息在模型和数据库之间不匹配的情况
以上这些警告信息需要将`engine.ShowWarn` 设置为 `true` 才会显示。
调用方法和Sync一样:
err := engine.Sync2(new(User), new(Group))
Create
role := Role{Name: "User"}
aff,err := engine.Insert(&role)
在插入单条数据成功后,如果该结构体有自增字段(设置为autoincr),则自增字段会被自动赋值为数据库中的id。这里需要注意的是,如果插入的结构体中,自增字段已经赋值,则该字段会被作为非自增字段插入。
插入同一个表的多条数据,此时如果数据库支持批量插入,那么会进行批量插入,但是这样每条记录就无法被自动赋予id值。如果数据库不支持批量插入,那么就会一条一条插入。
roles := make([]Role, 4)
roles[0].Name = "UserA"
roles[1].Name = "UserB"
roles[2].Name = "UserC"
roles[3].Name = "UserD"
aff,err := engine.Insert(&roles)
这里虽然支持同时插入,但这些插入并没有事务关系。因此有可能在中间插入出错后,后面的插入将不会继续。此时前面的插入已经成功,如果需要回滚,请开启事务。
Read
查询和统计主要使用Get
, Find
, Count
, Rows
, Iterate
这几个方法,同时大部分函数在调用Update
, Delete
时也是可用的。在进行查询时可以使用多个方法来形成查询条件,条件函数如下:
- Alias(string)
给Table设定一个别名
bo, _ := engine.Alias("ro").Where("ro.id=?", 5).Get(&roles)
and条件查询
bo, _ := engine.Alias("ro").Where("ro.id=?", 5).And("ro.name=?","Admin").Get(&roles)
or条件查询也是同理
判断某个记录是否存在可以使用Exist
, 相比Get
,Exist
性能更好。
join
type TestUser struct {
Id int `xorm:"pk autoincr"`
Name string `xorm:"varchar(255) not null"`
Role int `xorm:"int not null"`
}
type Result struct {
Name string
Role string
}
func (Result)TableName() string {
return "test_user"
}
_ = engine.Select("role.name as role,test_user.username as name").Join("left","role", "role.id=test_user.role").Find(&res)
fmt.Println(res)
update
更新数据使用Update
方法,Update方法的第一个参数为需要更新的内容,可以为一个结构体指针或者一个Map[string]interface{}类型。当传入的为结构体指针时,只有非空和0的field才会被作为更新的字段。当传入的为Map类型时,key为数据库Column的名字,value为要更新的内容。
var res Role
res.Name = "haha"
aff,_ := engine.Where("id=?",2).Update(&res)
fmt.Println(aff)
delete
删除数据Delete
方法,参数为struct的指针并且成为查询条件。
var ro Role
aff,err := engine.Where("id=?",1).Delete(&ro)
if err != nil {
fmt.Println("err: ",err)
}
fmt.Println(aff)
值得注意的是,如果在struct中定义一个deleted
,是可以造成软删除的,即不真正删除数据,只是加一个删除的标记,在查询时也不会有这条数据。如果想要真正的删除,需要启用Unscoped
engine.Id(1).Unscoped().Get(&ro)
engine.Id(1).Unscoped().Delete(&ro)
- Post link: https://www.godhearing.cn/xorm-ru-men/
- Copyright Notice: All articles in this blog are licensed under unless otherwise stated.