极客兔兔七天系列学习笔记--GeeORM
ORM框架实现的是对象到数据库表的映射,对于任意对象的属性解析,反射恰逢其时。
Session
想要对数据库进行操作,我们在连接数据库后可以新建一个对话。在go语言中直接用db对象也是可以的。但经过Session封装后我们可以做更多的事情:
- Engine可以随时新建Session,进行并发。
- Session独立管理他所连接的数据库和对象,这一切都是可高度定制的。
- Session维护sql.Tx,选择开启事务与否。
点击查看代码
type Session struct {
db *sql.DB
tx *sql.Tx
refTable *schema.Schema
clause clause.Clause
dialect dialect.Dialect
sql strings.Builder
sqlVars []interface{}
}
对象->表结构
对象有许多属性,包括属性名,类型和标识。我们要解析对象,获取到这些信息,才能操作数据库,例如下面的例子:
type User struct {
Name string `geeorm:"PRIMARY KEY"`
Age int
}
对应schema语句:
CREATE TABLE
User (
Nametext PRIMARY KEY,
Age integer);
通过反射我们可以很方便地获得每一个属性的信息,并且构造一个Schema结构体来封装相关信息。
点击查看代码
type Schema struct {
Model interface{}
Name string
Fields []*Field
FieldNames []string
fieldMap map[string]*Field
}
func Parse(dest interface{}, dialect dialect.Dialect) *Schema {
modelType := reflect.Indirect(reflect.ValueOf(dest)).Type()
schema := &Schema{
Name: modelType.Name(),
Model: dest,
FieldMap: make(map[string]*Field),
}
for i := 0; i < modelType.NumField(); i++ {
p := modelType.Field(i)
if p.Anonymous && ast.IsExported(p.Name) {
field := &Field{
Name: p.Name,
Type: dialect.DataTypeOf(reflect.Indirect(reflect.New(p.Type))),
}
if v, ok := p.Tag.Lookup("geeorm"); ok {
field.Tag = v
}
schema.Fields = append(schema.Fields, field)
schema.FieldNames = append(schema.FieldNames, field.Name)
schema.FieldMap[p.Name] = field
}
}
return schema
}
Clause模块
对于ORM框架这是锦上添花的操作,因为原始的sql语句过于复杂,开发人员更愿意使用简单的操作,例如组装。
我们可以先构造生产分句小零件的功能函数,他们每一个只产生固定规格的分句,例如select,value,where,然后让一个大家伙通过组装的方式拼凑出完整的sql语句和所需变量
type Clause struct {
sql map[Type]string
sqlVars map[Type][]interface{}
}
func (clause *Clause) Set(name Type, vars ...interface{}) {
if clause.sql == nil {
clause.sql = make(map[Type]string)
clause.sqlVars = make(map[Type][]interface{})
}
sql, args := generators[name](vars...)
clause.sql[name] = sql
clause.sqlVars[name] = args
}
func (clause *Clause) Build(orders ...Type) (string, []interface{}) {
var sqls []string
var vars []interface{}
for _, order := range orders {
sqls = append(sqls, clause.sql[order])
vars = append(vars, clause.sqlVars[order]...)
}
return strings.Join(sqls, ","), vars
}
Hook
在可能增加功能的地方埋上钩子,选择将扩展的功能挂在到这个点,比如Password字段的脱敏处理。这里的钩子实现比较简单,我们预设了所有可能的钩子方法(字符串定义),通过反射判断对象的该方法是否合法并选择实现。前面我们通过Clause最终封装了完整的Insert等操作函数,现在只需要将方法插入操作的对应位置接口,如果钩子没有实现就不会被调用。这里我们有一个统一的函数模版:
const (
BeforeQuery = "BeforeQuery"
AfterQuery = "AfterQuery"
BeforeUpdate = "BeforeUpdate"
AfterUpdate = "AfterUpdate"
BeforeDelete = "BeforeDelete"
AfterDelete = "AfterDelete"
BeforeInsert = "BeforeInsert"
AfterInsert = "AfterInsert"
)
func (s *Session) CallMethod(name string, value interface{}) {
fn := reflect.ValueOf(s.RefTable().Model).MethodByName(name)
if value != nil {
fn = reflect.ValueOf(value).MethodByName(name)
}
params := []reflect.Value{reflect.ValueOf(s)}
if fn.IsValid() {
if values := fn.Call(params); len(values) > 0 {
if err, ok := values[0].Interface().(error); ok {
log.Error(err)
}
}
}
}