使用 Go 打造另一款简单实用的 ORM

本文作者:nopsky,投稿发布
初衷
市面上优秀的ORM已经很多了,例如gorm,xorm,sqlx等,已经足够满足我们日常使用的各种场景了 。但对于像我这样喜欢简单好用的人来说gorm,xorm就像一个庞然大物,里面很多功能我都用不上 。再加上由于工作的原因,我经常在php和Golang之间切换,PHP用的Laravel框架,已经习惯了Laravel的DB操作方式,故此有了自己写一个类似Laravel DB操作的ORM,希望能通过本文抛砖引玉,大家都能打造一款符合自己业务场景,简单好用的ORM
目标
在动手编写代码之前,我们先畅想一下实现完以后,最终的使用方式是什么样子,有目标才知道要怎么做 。下面是我想实现的效果
//注册数据库kdb.RegisterDatabase(cfg)通过手写SQL操作数据库
//查询操作kdb.Select("select * from user where id = ?", ?)//插入操作kdb.Insert("insert into user(uid,username) values(?, ?)", 1, "nopsky")//更新操作kdb.Insert("update user set username = ? where uid = ?", "nopsky", 1)//删除操作kdb.Delete("delete from user where uid = ?", 1)通过链式操作数据库
//查询kdb.Table("user").Where("uid",1)//通过map或者struct插入数据kdb.Table("user").Insert(user)//批量插入kdb.Table("user").MultiInsert(users)//通过map更新数据kdb.Table("user").Where("uid",1).update(map[string]interface{}{"username":"nopsky"})//删除kdb.Table("user").Where("uid",1).Delete()操作数据库目标已经有了,我们还需要考虑返回值的操作,我们希望返回值能有以下的映射方式
1.返回Slice
.ToArray()2.返回Map
.ToMap()3.返回Struct
.ToStruct(&Users)例如:
//返回单条数据, 并按照map[string][string]格式返回kdb.Table("user").where("uid", 1).First().ToMap()//返回单条数据, 并按照[]string的格式返回kdb.Table("user").where("uid", 1).First().ToArray()//返回单挑数据,并按照struct的格式返回kdb.Table("user").where("uid", 1).First().ToStruct()//返回多条数据, 并按照[]map[string]string格式返回kdb.Table("user").where("sex", 1).Get().ToMap()//返回多条数据, 并按照[]string的格式返回kdb.Table("user").where("sex", 1).Get().ToArray()//返回多条数据,并按照[]struct的格式返回kdb.Table("user").where("sex", 1).Get().ToStruct(&Users)//也可以指定字段返回kdb.Table("user").where("sex",1).Get("uid", "name").ToStruct(&Users)还有对应的事务操作
tx := kdb.BeginTransaction()tx.Select(....)tx.Table("user")...tx.Update(...)tx.Commit()tx.Rollback()OK,我们有了最终实现的ORM需要提供的功能,接下来就是逐步来实现了
第一步: 注册数据库
在实际业务场景当中,我们连接数据库的情况一般分为
1.单个数据库 2.主从数据库 3.根据功能业务模块不一样,连接不同的数据库
所以我们第一步要做的事就先把数据库注册并管理起来,以便我们再使用的时候可以方便的使用指定的数据库 。
我们先创建一个config.go文件,用来存储我们的数据配置
package kdbimport "time"type DBConfig struct { Name string //数据库连接别名 IsMaster bool //是否是主库 Driver string Dsn string MaxLifetime time.Duration MaxIdleConns int MaxOpenConns int}type KConfig struct { DBConfigList []DBConfig}创建一个manager.go,用于管理我们的数据库
package kdbimport ( "database/sql" "fmt" "math/rand" "strings" "time")const defaultGroupName = "MySQL"var m = newManager()type manager struct { dbs map[string]map[string][]*sql.DB}func newManager() *manager { m := new(manager) m.dbs = make(map[string]map[string][]*sql.DB) return m}//添加数据库func (m *manager) addDB(groupName string, isMaster bool, db *sql.DB) { dc := "master" if !isMaster { dc = "slave" } group, ok := m.dbs[groupName] if !ok { group = make(map[string][]*sql.DB) } if _, ok := group[dc]; ok { group[dc] = Append(group[dc], db) } else { group[dc] = []*sql.DB{db} } m.dbs[groupName] = group}//获取数据库func (m *manager) getDB(names ...string) (*sql.DB, error) { groupName := defaultGroupName dc := "master" if len(names) > 0 { name := names[0] segment := strings.Split(name, "::") groupName = segment[0] if len(segment) > 1 { dc = segment[1] } } if dbs, ok := m.dbs[groupName][dc]; ok { max := len(dbs) rand.Seed(time.Now().UnixNano()) i := rand.Intn(max) return dbs[i], nil } return nil, fmt.Errorf("DataBase `%s::%s` not found", groupName, dc)}//获取从库func (m *manager) getReadDB(names ...string) (*sql.DB, error) { groupName := defaultGroupName if len(names) > 0 { groupName = names[0] } name := fmt.Sprintf("%s::%s", groupName, "slave") return m.getDB(name)}


推荐阅读