简介
go(golang)轻量级ORM,零依赖,支持达梦(dm),金仓(kingbase),神通(shentong),南大通用(gbase),mysql,postgresql,oracle,mssql,sqlite数据库.
源码地址:https://gitee.com/chunanyong/zorm
作者博客:https://www.jiagou.com
实践项目
夜莺监控告警项目国产化数据库的支持
夜莺是一套滴滴团队主导开源的分布式高可用的运维监控系统。
源码地址:https://github.com/didi/nightingale
改造方案
夜莺使用的是xorm作为ORM库,不支持国产数据库。尝试过通过ODBC的方式连接达梦,操作时遇到了语法不兼容问题。之后又了解到zorm,这个本身天然支持国产数据库的ORM框架。
面前出现了两种选择: 一是继续尝试ODBC的方式,一库一库的调试,手动解决各种兼容问题。二是替换项目的orm库,改造一次,就可以把国产四库的支持全部搞定。想想还是后者比较香,改造走起!
改造过程
首先数据库初始化,夜莺项目有多个数据库
//声明存储多库连接信息的map,key是dbname,value是DBDao
var DB = map[string]*zorm.DBDao{}
func InitMySQL(names ...string) {
//读取mysql.yml数据库配置文件
confdir := path.Join(runner.Cwd, "etc")
mysqlYml := path.Join(confdir, "mysql.local.yml")
if !file.IsExist(mysqlYml) {
mysqlYml = path.Join(confdir, "mysql.yml")
}
confs := make(map[string]MySQLConf)
err := file.ReadYaml(mysqlYml, &confs)
if err != nil {
log.Fatalf("cannot read yml[%s]: %v", mysqlYml, err)
}
//多库,不同的name
count := len(names)
for i := 0; i < count; i++ {
conf, has := confs[names[i]]
if !has {
log.Fatalf("no such mysql conf: %s", names[i])
}
dbDaoConfig := zorm.DataSourceConfig{
//DSN 数据库的连接字符串
DSN: conf.Addr,
//数据库驱动名称:mysql,postgres,oci8,sqlserver,sqlite3,dm,kingbase 和DBType对应,处理数据库有多个驱动
DriverName: "kingbase",
//数据库类型(方言判断依据):mysql,postgresql,oracle,mssql,sqlite,dm,kingbase 和 DriverName 对应,处理数据库有多个驱动
DBType: "kingbase",
//MaxOpenConns 数据库最大连接数 默认50
MaxOpenConns: 50,
//MaxIdleConns 数据库最大空闲连接数 默认50
MaxIdleConns: 50,
//ConnMaxLifetimeSecond 连接存活秒时间. 默认600(10分钟)后连接被销毁重建.避免数据库主动断开连接,造成死连接.MySQL默认wait_timeout 28800秒(8小时)
ConnMaxLifetimeSecond: 600,
//PrintSQL 打印SQL.会使用FuncPrintSQL记录SQL
PrintSQL: conf.Debug,
}
// 根据dbDaoConfig创建dbDao, 一个数据库只执行一次,第一个执行的数据库为 defaultDao,后续zorm.xxx方法,默认使用的就是defaultDao
dbDao, _ := zorm.NewDBDao(&dbDaoConfig)
DB[names[i]] = dbDao
}
}
建立连接
通过传入不同的dbName 获取绑定连接的Ctx
func getNewCtx(dbName string) context.Context {
var ctx = context.Background()
var dbDao *zorm.DBDao = DB[dbName]
newCtx, err := dbDao.BindContextDBConnection(ctx)
if err != nil { //标记测试失败
log.Fatalf("错误:%v", err)
}
return newCtx
}
操作
zorm还有一个方便实用的点:
- 在codeGenerator.go 中配置好数据库的连接信息
- 如果只想初始化单张表,请修改codeGenerator_test.go 中TestCodeGenerator调用的code参数,为表名
- 单表初始化:执行go test -v codeGenerator_test.go codeGenerator.go -test.run TestCodeGenerator
整个库初始化:执行go test -v codeGenerator_test.go codeGenerator.go -test.run TestCodeGeneratorALL
生成器代码简单清晰,可根据自己的需求做一些调整,使最终生成的代码能一步到位。
生成效果
拿一个表举例
//table name
const NodeStructTableName = "node"
// NodeStruct
type Node struct {
zorm.EntityStruct
Id int64 `column:"id" json:"id"`
Pid int64 `column:"pid" json:"pid"`
Name string `column:"name" json:"name"`
Path string `column:"path" json:"path"`
Leaf int `column:"leaf" json:"leaf"`
Note string `column:"note" json:"note"`
}
func (entity *Node) GetTableName() string {
return NodeStructTableName
}
//GetPKColumnName 获取数据库表的主键字段名称.因为要兼容Map,只能是数据库的字段名称.
func (entity *Node) GetPKColumnName() string {
return "id"
}
增删改查
使用原生的sql语句,没有对sql语法做限制.语句使用Finder作为载体
//查询
var obj Node
//has, err := DB["mon"].Where(col+"=?", val).Get(&obj) 这是之前xorm的代码
//获取对应数据库的连接
newCtx := getNewCtx("mon")
finder := zorm.NewSelectFinder(NodeStructTableName)
finder.Append("WHERE "+col+"=?", val)
has, err := zorm.QueryRow(newCtx, finder, &obj)
//添加
node := Node{
Pid: 0,
Name: "cop",
Path: "cop",
Leaf: 0,
Note: "公司节点",
}
//_, err = DB["mon"].Insert(&node) 这是之前xorm的代码
//手动开启事务,匿名函数返回的error如果不是nil,事务就会回滚
_, err = zorm.Transaction(newCtx, func(newCtx context.Context) (interface{}, error) {
//具体操作
_, err = zorm.Insert(newCtx, &node)
return nil, err
})
if err != nil {
log.Fatalln("cannot insert node[cop]")
}
//修改
newCtx := getNewCtx("mon")
//手动开启事务,匿名函数返回的error如果不是nil,事务就会回滚
_, err := zorm.Transaction(newCtx, func(newCtx context.Context) (interface{}, error) {
_, err := zorm.Update(newCtx, obj)
//如果返回的err不是nil,事务就会回滚
return nil, err
})
//删除
//_, err := DB["mon"].Where("id=?", n.Id).Delete(new(Node))
//手动开启事务,匿名函数返回的error如果不是nil,事务就会回滚
_, err := zorm.Transaction(newCtx, func(newCtx context.Context) (interface{}, error) {
finder := zorm.NewDeleteFinder(NodeStructTableName)
finder.Append("WHERE id=?", n.Id)
_, err := zorm.UpdateFinder(newCtx, finder)
return nil, err
})
改造总结
zorm上手非常简单,改造过程也很顺畅。
以上只是zorm使用的部分示例,除此之外zorm还支持事务传播、批量操作、不方便使用struct的场景,以及读写分离等。更多使用场景请前往 zorm官方。
如有使用方面的疑问请下方留言,改造建议或想了解更多,请在最上方源码地址找官方,作者回复也很热情: )