这几天在用GoLang写个东西,期间用到了gorm框架。
在用gorm查询时遇到了一个奇怪的问题:就是这个框架会自动将model的名称转为复数形式。
先来看下具体的报错信息。这里是一个用户model:
1 2 3 4 5 6 7 8 9 10 11 12 |
// User 用户信息 type User struct { // Id value Id string `json:"id" gorm:"primaryKey"` // Username 用户名 Username string `json:"username"` // Password 密码 Password string `json:"password"` } |
查询语句在这里:
1 2 3 4 5 6 |
// GetById 根据ID查询用户信息 func (r *UserRepo) GetById(id int64) *model.User { var user model.User db.Last(&user, id) return &user } |
执行查询的时候收到了如下的错误信息:
1 2 |
Error 1146: Table 'zhyea.users' doesn't exist [1.083ms] [rows:0] SELECT * FROM `users` WHERE `users`.`id` = 1 ORDER BY `users`.`id` DESC LIMIT 1 |
错误信息显示查询的时候没有查预期中user表,反而是查了一个复数形式的users表。
跟踪了下gorm框架的代码,发现这里是获取表名的关键点:
1 2 3 4 5 6 7 |
// TableName convert string to table name func (ns NamingStrategy) TableName(str string) string { if ns.SingularTable { return ns.TablePrefix + ns.toDBName(str) } return ns.TablePrefix + inflection.Plural(ns.toDBName(str)) } |
关键在NamingStrategy
的ns.SingularTable
属性上。查了下资料后知道可以配置中设置相关的属性:
1 2 3 |
NamingStrategy: schema.NamingStrategy{ SingularTable: true, }, |
具体是在创建数据库连接时做这个配置。完整的数据库连接配置在这里:
1 2 3 4 5 6 7 8 9 10 11 12 |
db, err = gorm.Open(mysql.New(mysql.Config{ DSN: ds.Dsn, // DSN data source name DefaultStringSize: 256, // string 类型字段的默认长度 DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持 DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引 DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列 SkipInitializeWithVersion: true, // 根据当前 MySQL 版本自动配置 }), &gorm.Config{ NamingStrategy: schema.NamingStrategy{ SingularTable: true, }, }) |
倒数第4行代码是起点。(这是第一种方式)
后来又在gorm的文档中发现了其他两种方式:
- 为
User
model 创建一个TableName()
函数,大致如下:
1 2 3 4 |
// TableName 指定表名 func (User) TableName() string { return "user" } |
这是第二种方式。
- 在查询时临时指定表名
1 2 |
var user model.User db.Table("user").Last(&user, id) |
这是第三种方式。但是可以看出,这个方案并不好,谁有耐心在每次查询时都指定表名啊。
在gorm框架的schema.go
的ParseWithSpecialTableName()
方法(第123行)可以看到下面的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
modelValue := reflect.New(modelType) // 第一种方式 tableName := namer.TableName(modelType.Name()) if tabler, ok := modelValue.Interface().(Tabler); ok { // 第二种方式 tableName = tabler.TableName() } if en, ok := namer.(embeddedNamer); ok { // 第四种方式 tableName = en.Table } if specialTableName != "" && specialTableName != tableName { // 第三种方式 tableName = specialTableName } |
在这段代码里可以看到设置表名的几种方式和对应的优先级。这里除我们前面提到的三种设置表名的方式外,还提示了第四种方式:通过嵌入式命名规则。可以猜测下第四种方式的使用场景:比如在需要分表的场景下这种方式还是非常便捷的。不过现在对我来说前面三种方式已经完全够用了,第四种具体如何使用我就懒得探索了…
就这样了!END!!!
发表评论