上一篇文章我们已经知道了不使用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驱动真正的查询数据
  • 通过反射来组成对象或者是数组对象提供使用
  • 之后我们可以看一些复杂的操作,比如外键 预加载 多表查询等操作。

    有疑问加站长微信联系(非本文作者)

  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传
  • 上一篇文章我们已经知道了不使用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驱动真正的查询数据
  • 通过反射来组成对象或者是数组对象提供使用
  • 之后我们可以看一些复杂的操作,比如外键 预加载 多表查询等操作。

     最高记录 5390 ©2013-2023 studygolang.com Go语言中文网,中国 Golang 社区,致力于构建完善的 Golang 中文社区,Go语言爱好者的学习家园。 Powered by StudyGolang(Golang + MySQL)   · CDN 采用 七牛云 VERSION: V4.0.0 · 7.410293ms · 为了更好的体验,本站推荐使用 Chrome 或 Firefox 浏览器 京ICP备14030343号-1