ABLEGAO:生命不息,折腾不止。 / ablegao/orm 基于golang 的orm

一个基于Golang 的数据库ORM 设计史

2015-02-28 posted in []

原创内容 - 本文链接:http://ablegao.me/category/nado_orm/index.html

下载地址: http://github.com/server-nado/orm

这个库源自公司游戏服务器框架。当初第一版设计时,golang发型版本为1.1 , 市面上没有太多下成的orm . 我们用python搭建的webapi ,golang直接通过webapi操作数据库, 适用一段时间。当时负责写api 的绕了不少远路, 为每个module 独立一个操作借口,这样的结果是webapi部分一对的工作,golang部分又一堆的工作。一个地方变动,一堆变动。

后来,更新的第二版, 一个固定的webapi,用_db=DATABASENAME&_tb=TABLENAME&field=_xxx 这种方式实现数据库查询, 然后golang部分也实现了通过一个 struct 制定tag 的方式, 动态调用这个api。 到这一步, orm 其实已经很好用了, 专心设计数据库, 和专心写struct 就可以。 但问题出来了一些, api部分功能升级,牵扯到了golang库的变动, 就算尽量保持一致, 累的很难维护, 新项目时, 可能又要独立一套webapi(项目服务器完全分开)。当时设计的struct 操作方法, 也有些麻烦,比如(老方法, 于公开的版本无关):

//修改一个累的方法。
type UserInfo struct {
	Uid uint32 `json:"uid" index:"pk"`
	UserName string `json:"username" `
	Money int `json:"money"`
} 
u:=new(UserInfo)
u.Uid=1
mode:=orm.PlotCache(u)
mode.Set("UserName" , "xxxx")
mode.Save() 
mode.Incy("money",+1)
mode.Save()

u = new(UserInfo)
u.Uid=0
u.UserName="xxxx"
u.Money =111
orm.Save(u) //添加

u = new(UserInfo)
u.Uid=1
u.UserName="xxxx"
u.Money =111
orm.Save(u) //修改

看似还可以。 不过每次使用时, 你的代码就必须有一个mode 的变量在跟着转悠, orm变量来回调用, 代码量大了, 会让阅读很困难。 于是, 有了现在的发布的这一版ORM .

type UserInfo struct {
	orm.Object
	Uid uint32 `field:"uid" index:"pk"`
	UserName string `field:"string"`
	Money int `json:"money"`
}
func(self *UserInfo)GetTableName()string{
	return "database.tablename"
}




u:=new(UserInfo)
all,err:= u.Objects(u).All()  //提取所有

all,err:= u.Objects(u).Filter("Uid__gt" , 100).All()   //  Uid  > 100 的。 

u.Objects(u).Filter("Uid__gt",100).Limit(0,10).All() //取10条记录。 

param:= u.Objects(u).Filter("Uid__gt",100)
param.FilterOr("Money__lt", 500)
param.All() // Uid >100 or Money < 500的。 



u:=new(UserInfo)
u.Uid=1
u.Objects(u).One() //提取一个

u.UserName="111111"
u.Money = 1000
u.Save() //保存
u.Change("Money__add" , 1) //money +1 
u.Save() //保存。 

u.Delete() //删除掉

这样操作起来容易很多了。 还有更多的操作符, 可以看 https://github.com/server-nado/orm/blob/master/marsharl_driver_mysql.go 里面_w 函数方法

这个版本的orm , 直接使用的sql 驱动, 不在受webapi的限制。 而扩展数据库很容易, 可以参考marsharl_driver_mysql.go 里面的内容。

上面这些,并非这个orm的重点功能。频繁的数据库修改,和一些没必要必须摄入到数据库里面的数据缓存, 是这个ORM的重点。

所以这个ORM有另外一个Redis缓存扩展:

type UserInfo struct {
 		orm.CacheModule   //注意这里的变化
 		Uid uint32 `field:"uid" index:"pk" cache:"info" cache_profix:"user"`
 		UserName string `field:"string"`
 		Money int `json:"money"`
 	}
 	func(self *UserInfo)GetTableName()string{
 		return "database.tablename"
 	}

和上面的类区变化不大,更换了orm.Object 为orm.CacheModule . 增加cache tag , 和第一个字段增加 cache_profix.

很抱歉的是, 我很难保证orm.CacheModule 和orm.Object的通用性, 所以 如果要切换时, Filter方法有些字段不可用。 

u:=new(UserInfo)
u.Uid=1
u.Objects(u).One()

这样, redis中会增加一个key user:1:info 内容是hash 的userInfo所有字段数据。 

然后你下次在使用时:
u:=new(UserInfo)
u.Uid=1
u.Objects(u).One() //你的数据将会从Redis中提取, 而不是Mysql 


然后修改:
u.Incry("Money" , +1)
u.Save() //同步到数据库。 不执行 只变更redis


u.Set("Money" , "aaaaaa") //修改. 
u.Save() 

这样, 这个orm 算是实现了。 很多细节,待续。