这边主要说下ORM框架主要的几个部分:
1、Dialect
这个是每个数据库都需要提供dialect对象,并将其在启动的时候进行注册,这个部分主要是为了数据库和GO语言数据类型等其它差异提供转换函数:
SQL 语句中的类型和 Go 语言中的类型是不同的,例如Go 语言中的 int
、int8
、int16
等类型均对应 SQLite 中的 integer
类型。因此实现 ORM 映射的第一步,需要思考如何将 Go 语言的类型映射为数据库中的类型。
同时,不同数据库支持的数据类型也是有差异的,即使功能相同,在 SQL 语句的表达上也可能有差异。ORM 框架往往需要兼容多种数据库,因此我们需要将差异的这一部分提取出来,每一种数据库分别实现,实现最大程度的复用和解耦。这部分代码称之为 dialect
package dialect
import "reflect"
var dialectsMap = map[string]Dialect{}
type Dialect interface {
DataTypeOf(typ reflect.Value) string
TableExistSQL(tableName string) (string, []interface{})
}
func RegisterDialect(name string, dialect Dialect) {
dialectsMap[name] = dialect
}
func GetDialect(name string) (dialect Dialect, ok bool) {
dialect, ok = dialectsMap[name]
return
}
比如,sqlite3提供DataTypeOf和TableExistSQL函数
package dialect
import (
"fmt"
"reflect"
"time"
)
type sqlite3 struct{}
var _ Dialect = (*sqlite3)(nil)
func init() {
RegisterDialect("sqlite3", &sqlite3{})
}
func (s *sqlite3) DataTypeOf(typ reflect.Value) string {
switch typ.Kind() {
case reflect.Bool:
return "bool"
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uintptr:
return "integer"
case reflect.Int64, reflect.Uint64:
return "bigint"
case reflect.Float32, reflect.Float64:
return "real"
case reflect.String:
return "text"
case reflect.Array, reflect.Slice:
return "blob"
case reflect.Struct:
if _, ok := typ.Interface().(time.Time); ok {
return "datetime"
}
}
panic(fmt.Sprintf("invalid sql type %s (%s)", typ.Type().Name(), typ.Kind()))
}
func (s *sqlite3) TableExistSQL(tableName string) (string, []interface{}) {
args := []interface{}{tableName}
return "SELECT name FROM sqlite_master WHERE type='table' and name = ?", args
}
2、Schema
Schema主要就是提供将go对象转换为一种数据库能够识别的格式的方法,反过来,会将数据库查询的结果反向生成GO对象
Dialect 实现了一些特定的 SQL 语句的转换,Schema是 ORM 框架中最为核心的转换——对象(object)和表(table)的转换。给定一个任意的对象,转换为关系型数据库中的表结构。
在数据库中创建一张表需要哪些要素呢?
- 表名(table name) —— 结构体名(struct name)
- 字段名和字段类型 —— 成员变量和类型。
- 额外的约束条件(例如非空、主键等) —— 成员变量的Tag(Go 语言通过 Tag 实现,Java、Python 等语言通过注解实现)
package schema
import (
"geeorm/dialect"
"go/ast"
"reflect"
)
// Field represents a column of database
type Field struct {
Name string
Type string
Tag string
}
// Schema represents a table of database
type Schema struct {
Model interface{}
Name string
Fields []*Field
FieldNames []string
fieldMap map[string]*Field
}
func (schema *Schema) GetField(name string) *Field {
return schema.fieldMap[name]
}
func Parse(dest interface{}, d dialect.Dialect) *Schema {
modelType := reflect.Indirect(reflect.ValueOf(dest)).Type()
schema := &Schema{
Model: dest,
Name: modelType.Name(),
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: d.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, p.Name)
schema.fieldMap[p.Name] = field
}
}
return schema
}
3、Session
Session 的核心功能是与数据库进行交互。因此,我们将数据库表的增/删操作实现在子包 session 中。在此之前,Session 的结构需要做一些调整。
其中获取NewSession().Model(&User{})方法就会调用Schema中的parse将go的数据表对象转为Schema对象,然后通过Schema中的参数生成原生数据库语句
func (s *Session) Model(value interface{}) *Session {
// nil or different model, update refTable
if s.refTable == nil || reflect.TypeOf(value) != reflect.TypeOf(s.refTable.Model) {
s.refTable = schema.Parse(value, s.dialect)
}
return s
}
4、Engine
Engine主要负责数据库的连接和关闭
type Engine struct {
db *sql.DB
dialect dialect.Dialect
}
func NewEngine(driver, source string) (e *Engine, err error) {
db, err := sql.Open(driver, source)
if err != nil {
log.Error(err)
return
}
// Send a ping to make sure the database connection is alive.
if err = db.Ping(); err != nil {
log.Error(err)
return
}
// make sure the specific dialect exists
dial, ok := dialect.GetDialect(driver)
if !ok {
log.Errorf("dialect %s Not Found", driver)
return
}
e = &Engine{db: db, dialect: dial}
log.Info("Connect database success")
return
}
func (engine *Engine) NewSession() *session.Session {
return session.New(engine.db, engine.dialect)
}