![86602573f493c956d7755a4914b91fb8.png](https://i-blog.csdnimg.cn/blog_migrate/840b579ea09df0a5a46efccb6e282caa.jpeg)
- 简介
- 模型交互
- AutoMigrate
- createTable
- callbacks
- 实际注册流程
- createCallback
- 总结
简介
GORM 源码解读, 基于 v1.9.11 版本.
模型交互
前面已经研究过模型是如何定义并被解析的了, 这次看一下模型是如何和数据库交互的.
package main
import (
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/sqlite"
)
type Product struct {
gorm.Model
Code string
Price uint
}
func main() {
db, err := gorm.Open("sqlite3", "test.db")
if err != nil {
panic("failed to connect database")
}
defer db.Close()
// Migrate the schema
db.AutoMigrate(&Product{})
// 创建
db.Create(&Product{
Code: "L1212", Price: 1000})
// 读取
var product Product
db.First(&product, 1) // 查询id为1的product
db.First(&product, "code = ?", "L1212") // 查询code为l1212的product
// 更新 - 更新product的price为2000
db.Model(&product).Update("Price", 2000)
// 删除 - 删除product
db.Delete(&product)
}
AutoMigrate
当定义好模型之后, 第一步是使用 AutoMigrate
合并模型:
db.AutoMigrate(&Product{})
看一下它的源码:
// AutoMigrate run auto migration for given models, will only add missing fields, won't delete/change current data
func (s *DB) AutoMigrate(values ...interface{}) *DB {
db := s.Unscoped()
for _, value := range values {
db = db.NewScope(value).autoMigrate().db
}
return db
}
内部是对每个传递的参数调用了 db.NewScope(value).autoMigrate()
.
那具体是如何合并的呢?
func (scope *Scope) autoMigrate() *Scope {
tableName := scope.TableName()
quotedTableName := scope.QuotedTableName()
if !scope.Dialect().HasTable(tableName) {
scope.createTable()
} else {
for _, field := range scope.GetModelStruct().StructFields {
if !scope.Dialect().HasColumn(tableName, field.DBName) {
if field.IsNormal {
sqlTag := scope.Dialect().DataTypeOf(field)
scope.Raw(fmt.Sprintf("ALTER TABLE %v ADD %v %v;", quotedTableName, scope.Quote(field.DBName), sqlTag)).Exec()
}
}
scope.createJoinTable(field)
}
scope.autoIndex()
}
return scope
}
中间的 if 部分的代码展示了两条路径. 如果表还没有创建, 直接创建就行了.
否则就需要对模型中的每个字段进行操作, 如果列名不存在, 就需要变更表新增字段了.
scope.Raw(fmt.Sprintf("ALTER TABLE %v ADD %v %v;", quotedTableName, scope.Quote(field.DBName), sqlTag)).Exec()
SQL 语句是如何执行的, 先暂时不理会, 但从代码的形式上看算是挺简洁的, 直接使用 Raw 构造语句, Exec 执行.
同时, 对于模型中的每个字段, 还要更新一遍连接表, scope.createJoinTable(field)
.
在 for 循环处理完模型中的所有字段后, 再更新一遍索引, scope.autoIndex()
.
总结起来, 自动合并主要做了这么几件事: 创建表, 添加新增的字段, 更新表的关系, 更新索引.
createTable
前面省略了创建表的具体过程, 来仔细看看表是如何创建的.
func (scope *Scope) createTable() *Scope {
var tags []string
var primaryKeys []string
var primaryKeyInColumnType = false
for _, field := range scope.GetModelStruct().StructFields {
if field.IsNormal {
sqlTag := scope.Dialect().DataTypeOf(field)
// Check if the primary key constraint was specified as
// part of the column type. If so, we can only support
// one column as the primary key.
if strings.Contains(strings.ToLower(sqlTag), "primary key") {
primaryKeyInColumnType = true
}
tags = append(tags, scope.Quote(field.DBName)+" "+sqlTag)
}
if field.IsPrimaryKey {
primaryKeys = append(primaryKeys, scope.Quote(field.DBName))
}
scope.createJoinTable(field)
}
var primaryKeyStr string
if len(primaryKeys) > 0 && !primaryKeyInColumnType {
primaryKeyStr = fmt.Sprintf(", PRIMARY KEY (%v)", strings.Join(primaryKeys, ","))
}
scope.Raw(fmt.Sprintf("CREATE TABLE %v (%v %v)%s", scope.QuotedTableName(), strings.Join(tags, ","), primaryKeyStr, scope.getTableOptions())).Exec()
scope.autoIndex()
return scope
}
这就是构建 SQL 创建表的过程, 主要的过程是这行代码:
scope.Raw(fmt.Sprintf("CREATE TABLE %v (%v %v)%s", scope.QuotedTableName(), strings.Join(tags, ","), primaryKeyStr, scope.getTableOptions())).Exec()
前面的过程主要是遍历模型的字段, 获取每个字段的 sqlTag
, 并加入 tags 中:
tags = append(tags, scope.Quote(field.DBName)+" "+sqlTag)
带有双引号的列名加上空格加上 sqlTag
.
这个过程中还涉及到了主键的判断, 不过感觉这部分有点坑, 因为 sqlTag := scope.Dialect().DataTypeOf(field)
的实现取决于每种数据库对 DataTypeOf
的具体实现.
issues 2270 显示出现多个 primary key
, 使用的是如下的模型定义, 数据库使用了 sqlite3:
type Permission struct {
ID int64 `gorm:"AUTO_INCREMENT;column:id;primary_key"`
Name string `gorm:"column:name;type:varchar;unique;not null"`
Idx int64 `gorm:"AUTO_INCREMENT"`
}
虽然这个模型定义中只指定了一个 primary_key
, 但结果 Idx
也变成了 primary_key
:
[2019-01-19 19:40:30] table "permission" has more than one primary key
[2019-01-19 19:40:30] [0.14ms] CREATE TABLE "permission" ("id" integer primary key autoincrement,"name" varchar NOT NULL UNIQUE,"idx" integer primary key autoincrement )
[0 rows affected or returned ]
原因只有一个, 它使用了 AUTO_INCREMENT
选项, 而在 sqlite3 的 DataTypeOf
实现中:
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uintptr:
if s.fieldCanAutoIncrement(field) {
field.TagSettingsSet("AUTO_INCREMENT", "AUTO_INCREMENT")
sqlType = "integer primary key autoincrement"
} else {
sqlType = "integer"
}
case reflect.Int64, reflect.Uint64:
if s.fieldCanAutoIncrement(field) {
field.TagSettingsSet("AUTO_INCREMENT", "AUTO_INCREMENT")
sqlType = "integer primary key autoincrement"
} else {
sqlType = "bigint"
}
AUTO_INCREMENT
选项导致了返回的结果中存在 primary key
.
我怀疑这是个 bug. 因为在后续有对是否是主键的判断, 并添加 primaryKeyStr
.
if field.IsPrimaryKey {
primaryKeys = append(primaryKeys, scope.Quote(field.DBName))
}
var primaryKeyStr string
if len(primaryKeys) > 0 && !primaryKeyInColumnType {
primaryKeyStr = fmt.Sprintf(", PRIMARY KEY (%v)", strings.Join(primaryKeys, ","))
}
我觉得 sqlType
不应该返回关于 p