简单使用
上一篇文章我们已经知道了不使用orm如何调用mysql数据库,这篇文章我们要查看的是Gorm的源码,从最简单的一个查询语句作为切入点。当然Gorm的功能很多支持where条件支持外键group等等功能,这些功能大体的流程都是差不多先从简单的看起。下面先看如何使用
package main
import (
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/panlei/gorm"
)
var db *gorm.DB
func main() {
InitMysql()
var u User
db.Where("Id = ?", 2).First(&u)
}
func InitMysql() {
var err error
db, err = gorm.Open("mysql", "root:***@******@tcp(**.***.***.***:****)/databasename?charset=utf8&loc=Asia%2FShanghai&parseTime=True")
fmt.Println(err)
}
type User struct {
Id int `gorm:"primary_key;column:Id" json:"id"`
UserName string `json:"userName" gorm:"column:UserName"`
Password string `json:"password" gorm:"column:Password"`
}
func (User) TableName() string {
return "user"
}
- 首先注册对象 添加tag标注主键 设置数据库column名 数据库名可以和字段名不一样
- 设置table名字,如果类名和数据库名一样则不需要设置
- 初始化数据库连接 创建GormDB对象 使用Open方法返回DB
- 使用最简单的where函数和First来获取 翻译过来的sql语句就是
select * from user where Id = 2 limit 1
源码分析
1. DB、search、callback对象
DB对象包含所有处理mysql的方法,主要的还是search和callbacks
search对象存放了所有的查询条件
Callback 对象存放了sql的调用链 存放了一系列的callback函数
// Gorm中使用的DB对象
type DB struct {
sync.RWMutex // 锁
Value interface{}
Error error
RowsAffected int64
// single db
db SQLCommon // 原生db.sql对象,包含query相关的原生方法
blockGlobalUpdate bool
logMode logModeValue
logger logger
search *search // 保存搜索的条件where, limit, group,比如调用db.clone()时,会指定search
values sync.Map
// global db
parent *DB
callbacks *Callback // 当前sql绑定的函数调用链
dialect Dialect // 不同数据库适配注册sql.db
singularTable bool
}
// search 对象存放了所有查询的条件 从名字就能看出来 有where or having 各种条件
type search struct {
db *DB
whereConditions []map[string]interface{}
orConditions []map[string]interface{}
notConditions []map[string]interface{}
havingConditions []map[string]interface{}
joinConditions []map[string]interface{}
initAttrs []interface{}
assignAttrs []interface{}
selects map[string]interface{}
omits []string
orders []interface{}
preload []searchPreload
offset interface{}
limit interface{}
group string
tableName string
raw bool
Unscoped bool
ignoreOrderQuery bool
}
// Callback记录了调用链 区分了update delete query create等不同
// 这些callback都在callback.go中的init 方法中注册
type Callback struct {
logger logger
creates []*func(scope *Scope)
updates []*func(scope *Scope)
deletes []*func(scope *Scope)
queries []*func(scope *Scope)
rowQueries []*func(scope *Scope)
processors []*CallbackProcessor
}
2. Scope对象 每一个sql操作所有的信息
// 包含每一个sql操作的相关信息
type Scope struct {
Search *search // 检索条件在1中是同一个对象
Value interface{} // 保存实体类
SQL string // sql语句
SQLVars []interface{}
db *DB // DB对象
instanceID string
primaryKeyField *Field
skipLeft bool
fields *[]*Field // 字段
selectAttrs *[]string
}
3. Open函数 创建数据库DB对象初始化数据库连接
Open函数主要是根据输入的数据库信息
- 创建连接
- 初始化DB对象
- 设置调用链函数
- 发送一个ping 测试是否能可用
func Open(dialect string, args ...interface{}) (db *DB, err error) {
if len(args) == 0 {
err = errors.New("invalid database source")
return nil, err
}
var source string
// 接口对应database/sql接口
var dbSQL SQLCommon
var ownDbSQL bool
switch value := args[0].(type) {
// 如果第一个参数是string 则使用sql.open 创建连接 返回sql.Db 对象
case string:
var driver = dialect
if len(args) == 1 {
source = value
} else if len(args) >= 2 {
driver = value
source = args[1].(string)
}
dbSQL, err = sql.Open(driver, source)
ownDbSQL = true
// 如果是SQLCommon 直接赋值
case SQLCommon:
dbSQL = value
ownDbSQL = false
default:
return nil, fmt.Errorf("invalid database source: %v is not a valid type", value)
}
// 初始化DB对象
db = &DB{
db: dbSQL,
logger: defaultLogger,
// 在callback_create.go
// callback_deleta.go
// callback_query.go
// callback_save.go
// callback_update.go 等等 注册了默认的callback
// callback文件中的init方法中注册了默认的callback方法
// 主要处理的逻辑几乎都在各个不同的callback中
callbacks: DefaultCallback,
dialect: newDialect(dialect, dbSQL),
}
db.parent = db
if err != nil {
return
}
// 发送一个ping 确认这个连接是可用的
if d, ok := dbSQL.(*sql.DB); ok {
if err = d.Ping(); err != nil && ownDbSQL {
d.Close()
}
}
return
}
4. where 函数 创建数据库DB对象初始化数据库连接
其实跟where相同的还有很多比如having、group、limit、select、or、not等等其实操作都是类似的
调用DB对象函数where 在调用search对象的where
具体就是把where条件放到search对象中的whereConditions中等最后拼接sql
func (s *DB) Where(query interface{}, args ...interface{}) *DB {
return s.clone().search.Where(query, args...).db
}
func (s *search) Where(query interface{}, values ...interface{}) *search {
s.whereConditions = append(s.whereConditions, map[string]interface{}{"query": query, "args": values})
return s
}
4. First 函数
- First 创建一个Scpoe
- NewScope 中调用DB.clone 函数 克隆DB对象 底层指针属性不变
- inlineCondition 初始化查询条件
- callCallbacks函数,传入调用链 for循环调用传入的函数
func (s *DB) First(out interface{}, where ...interface{}) *DB {
newScope := s.NewScope(out)
newScope.Search.Limit(1)
// callCallbacks调用query callback方法
return newScope.Set("gorm:order_by_primary_key", "ASC").
inlineCondition(where...).callCallbacks(s.parent.callbacks.queries).db
}
// 新建Scope
func (s *DB) NewScope(value interface{}) *Scope {
// 克隆DB 对象
dbClone := s.clone()
dbClone.Value = value
scope := &Scope{db: dbClone, Value: value}
if s.search != nil {
scope.Search = s.search.clone()
} else {
scope.Search = &search{}
}
return scope
}
func (scope *Scope) inlineCondition(values ...interface{}) *Scope {
if len(values) > 0 {
scope.Search.Where(values[0], values[1:]...)
}
return scope
}
// 循环调用传入的functions
func (scope *Scope) callCallbacks(funcs []*func(s *Scope)) *Scope {
defer func() {
if err := recover(); err != nil {
if db, ok := scope.db.db.(sqlTx); ok {
db.Rollback()
}
panic(err)
}
}()
// 使用for循环 调用回调函数
for _, f := range funcs {
(*f)(scope)
if scope.skipLeft {
break
}
}
return scope
}
5. 真正查询方法queryCallback
- queryCallback 方法组成sql语句 调用database/sql 中的query方法在上一篇分析中可以看到 循环rows结果获取数据
- prepareQuerySQL方法主要是组成sql语句的方法 通过反射获取字段名表明等属性
func queryCallback(scope *Scope) {
if _, skip := scope.InstanceGet("gorm:skip_query_callback"); skip {
return
}
//we are only preloading relations, dont touch base model
if _, skip := scope.InstanceGet("gorm:only_preload"); skip {
return
}
defer scope.trace(NowFunc())
var (
isSlice, isPtr bool
resultType reflect.Type
results = scope.IndirectValue()
)
// 找到排序字段
if orderBy, ok := scope.Get("gorm:order_by_primary_key"); ok {
if primaryField := scope.PrimaryField(); primaryField != nil {
scope.Search.Order(fmt.Sprintf("%v.%v %v", scope.QuotedTableName(), scope.Quote(primaryField.DBName), orderBy))
}
}
if value, ok := scope.Get("gorm:query_destination"); ok {
results = indirect(reflect.ValueOf(value))
}
if kind := results.Kind(); kind == reflect.Slice {
isSlice = true
resultType = results.Type().Elem()
results.Set(reflect.MakeSlice(results.Type(), 0, 0))
if resultType.Kind() == reflect.Ptr {
isPtr = true
resultType = resultType.Elem()
}
} else if kind != reflect.Struct {
scope.Err(errors.New("unsupported destination, should be slice or struct"))
return
}
// 准备查询语句
scope.prepareQuerySQL()
if !scope.HasError() {
scope.db.RowsAffected = 0
if str, ok := scope.Get("gorm:query_option"); ok {
scope.SQL += addExtraSpaceIfExist(fmt.Sprint(str))
}
// 调用database/sql 包中的query来查询
if rows, err := scope.SQLDB().Query(scope.SQL, scope.SQLVars...); scope.Err(err) == nil {
defer rows.Close()
columns, _ := rows.Columns()
// 循环rows 组成对象
for rows.Next() {
scope.db.RowsAffected++
elem := results
if isSlice {
elem = reflect.New(resultType).Elem()
}
scope.scan(rows, columns, scope.New(elem.Addr().Interface()).Fields())
if isSlice {
if isPtr {
results.Set(reflect.Append(results, elem.Addr()))
} else {
results.Set(reflect.Append(results, elem))
}
}
}
if err := rows.Err(); err != nil {
scope.Err(err)
} else if scope.db.RowsAffected == 0 && !isSlice {
scope.Err(ErrRecordNotFound)
}
}
}
}
func (scope *Scope) prepareQuerySQL() {
// 如果是rwa 则组织sql语句
if scope.Search.raw {
scope.Raw(scope.CombinedConditionSql())
} else {
// 组织select 语句
// scope.selectSQL() 组织select 需要查询的字段
// scope.QuotedTableName() 获取表名
// scope.CombinedConditionSql()组织条件语句
scope.Raw(fmt.Sprintf("SELECT %v FROM %v %v", scope.selectSQL(), scope.QuotedTableName(), scope.CombinedConditionSql()))
}
return
}
总结
这篇文章从一个最简单的where条件和first函数入手了解Gorm主体的流程和主要的对象。其实可以看出Gorm的本质:
- 创建DB对象,注册mysql连接
- 创建对象 通过tag设置一些主键,外键等
- 通过where或者其他比如group having 等设置查询的条件
- 通过first函数最终生成sql语句
- 调用database/sql 中的方法通过mysql驱动真正的查询数据
- 通过反射来组成对象或者是数组对象提供使用
之后我们可以看一些复杂的操作,比如外键 预加载 多表查询等操作。