大家好,我是渔夫子。
在sql中首先要指定是从哪张表中查询。今天就来看一下gorm是如何根据model来自动解析表名的。
我们看如下例子:
type MTest struct {
Id int64
Name string
}
func main() {
dsn := "test:123456@tcp(localhlost:3306)/test01?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms"
db, _ := gorm.Open(mysql.Open(dsn))
m := MTest{}
db.Find(m)
fmt.Println(m)
fmt.Println(db.Statement.Table)
}
在这个例子中,表名是通过根据model的结构体的名字MTest来指定的。规则如下:
所以,上面的表名最终是:m_tests
这种方式的实现,本质上是通过gorm.Open中指定的名称策略来实现的。如下:
在NamingStrategy结构体中,可以指定前缀以及是否是复数形式,如下:
func (ns NamingStrategy) TableName(str string) string {
if ns.SingularTable {
return ns.TablePrefix + ns.toDBName(str)
}
return ns.TablePrefix + inflection.Plural(ns.toDBName(str))
}
通过在gorm.Open的函数中指定对应的配置选项,就可以给本次连接做相关的配置。比如指定表名前缀,禁用表名复数等。如下:
type MTest struct {
Id int64
Name string
}
func main() {
dsn := "sands:123456@tcp(test.trdplace.ads.sg1.mysql:3306)/test01?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms"
config := &gorm.Config{
NamingStrategy: schema.NamingStrategy{
TablePrefix: "pre_", // 表前缀
SingularTable: true, // 禁用表名复数
}}
db, _ := gorm.Open(mysql.Open(dsn), config)
m := MTest{}
db.Find(m)
fmt.Println(m)
fmt.Println(db.Statement.Table)
}
如上,最终打印出的表名就是m_test
。
第二种方式是通过让model结构体实现TableName() string 函数来指定具体的表名。如下:
type MTest struct {
Id int64
Name string
}
func (m *MTest) TableName() string {
return "m_test"
}
这样,表名就是TableName指定的字符串,指定什么就是什么。其实这个本质上是实现了Tabler接口:
type Tabler interface {
TableName() string
}
第三种方式是让Model结构体实现TablerWithNamer接口来指定表名。接口定义如下:
type TablerWithNamer interface {
TableName(Namer) string
}
这里的Namer也是一个接口,在第一种方式中,实际上就是指定了一个Namer,在gorm.Open函数中,我们看下:
当然,我们也可以自己实现Namer接口来指定具体的表名规则。
这个是在Namer接口的基础上,如果该Namer是embeddedNamer结构体类型,那么优先使用这个结构体中指定的名称。embeddedNamer结构体如下:
type embeddedNamer struct {
Table string
Namer
}
使用db.Table
指定的表后,gorm将不再使用自动解析出来的表名了。所以该方式的优先级是最高的。如下:
type MTest struct {
Id int64
Name string
}
func main() {
dsn := "123456:123456@tcp(localhost:3306)/test01?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms"
db, _ := gorm.Open(mysql.Open(dsn))
m := MTest{}
db.Table("m_tests").Find(m)
}
还是以db.Find(m)为例。首先,Find函数会把m对象赋值给db.Statement.Dest字段。这个Dest字段就是要查询的目标数据。然后再调用db.callbacks.Query().Execute(tx)。就是实际的查询函数。如下是db.Find的代码:
func (db *DB) Find(dest interface{}, conds ...interface{}) (tx *DB) {
tx = db.getInstance()
if len(conds) > 0 {
if exprs := tx.Statement.BuildCondition(conds[0], conds[1:]...); len(exprs) > 0 {
tx.Statement.AddClause(clause.Where{Exprs: exprs})
}
}
tx.Statement.Dest = dest
return tx.callbacks.Query().Execute(tx)
}
其中tx是从原db中再获取一个db对象,同时也具有了对应的数据库连接的对象。然后tx.callbacks.Query()其实就是db.Open函数中初始化的callbacks:db.callbacks = initializeCallbacks(db)�
。如下:
func initializeCallbacks(db *DB) *callbacks {
return &callbacks{
processors: map[string]*processor{
"create": {db: db},
"query": {db: db},
"update": {db: db},
"delete": {db: db},
"row": {db: db},
"raw": {db: db},
},
}
}
其次,在processor.Execute函数中会看到,statement.Model和statement.Dest会相互赋值。如下:
最主要的是在statement.Parse函数解析的表名了。依次看代码调用,最终会到schema.go文件中的ParseWithSpecialTableName函数中,在这个函数中,有对表名的解析。如下:
本文总结了gorm中如何指定表名的多种方式。其中优先级最高的是通过gorm会根据指定的model结构体的名称自动解析出表名。同时,如果model实现了Tabler接口或TablerWithNamer接口,那么就会优先根据这两个接口的对应TableName函数指定的表名进行解析。