写这篇文章的初衷是由于自己一直对数据库的一些基本知识了解得不是很透彻,加上gorm文档中对数据库完整性描述简直让人摸不着头脑。趁着这个机会,我们来整合一下这些知识,为后面更深入地使用gorm打下基础。其次,可能许多同学在阅读gorm的文档时,也有同样的疑惑,希望大家一起讨论,共同进步。
温故而知新一
超键:是一个或者多个属性的集合,这些属性的组合可以使我们在一个关系中唯一地标识一个元组。
候选键: 去掉冗余属性之后的最小超键。
主键:用来标示某个元组在它所存在的关系中是是唯一的。通俗来说,主键是介于超键和候选键之间的key。
主键约束(非空且唯一)
首先我们来看看数据库完整性规范中的第一条,实体完整性约束:每个表有且仅有一个主键,每一个主键值必须唯一,而且不允许为“空”(NULL)或重复。我们首先对Product结构体进行自动迁移,Debug方法可以帮助我们打印sql语句,调试起来很方便。
var (
db *gorm.DB
err error
)
type Product struct {
gorm.Model // grom.Model是gorm预定义的结构,用于实现软删除
Code string `gorm:"primary_key"`
Price uint
}
//type Model struct {
// ID uint `gorm:"primary_key"`
// CreatedAt time.Time
// UpdatedAt time.Time
// DeletedAt *time.Time `sql:"index"`
//}
func main() {
db, err := gorm.Open("postgres", "host=localhost user=testuser dbname=testdb sslmode=disable password=123456")
defer db.Close()
fmt.Println(err)
if err == nil {
db.Debug().AutoMigrate(&Product{})
}
//CREATE TABLE "products" ("id" serial,"created_at" timestamp with time zone,"updated_at" timestamp with time zone,"deleted_at" timestamp with time zone,"code" text,"price" integer , PRIMARY KEY ("id","code"))
//CREATE INDEX idx_products_deleted_at ON "products"(deleted_at)
从 sql PRIMARY KEY (“id”,“code”)) 中,我们可以知道,在这次数据库迁移中,我们定义了联合主键。因此,我们可以下定论,Gorm实现了对数据库实体完整性支持,即可以支持字段主键,也可以支持联合主键。在这里,有同学可能觉得这是很稀松平常的事情,但是当你往下看,或许又是另一翻感悟。
在这里我们可以顺带提一下ORM中的命名规范,为工程项目建立规范标准,也是一件很重要的事情:
单数变复数 | 驼峰式的命名方法 | |
---|---|---|
规范 | 模型名统一为单数,对应的数据库表名为复数 | 若结构体名由多个单词组成,对应的数据表将使用下划线 |
示例 | Book(模型名称) —> books(数据表名) | BookClub(模型类名) —> book_clubs(数据表名) |
温故而知新二
外键: 一个关系模式(r1)可能在它的属性中包含另一个关系模式(r2)的主键,这个属性在r1上被称为外键,关系r1被称为外键依赖的参照关系,r2叫做外键依赖的被参照关系。
关联外键: 这个术语的英文叫AssociationForeignKey,可以用于指定被参照关系中(r2)中特定字段。
外键约束
Belongs To
首先我们来看看gorm文档中belongs to关系。belongs_to 关联创建两个模型之间一对一的关系,product belongs to user,声明所在的模型实例属于另一个模型的实例。 在我们下面声明的例子中,有商品和用户两个模型,而每个商品只能指定一个用户。
type Product struct {
Code string `gorm:"primary_key"`
Price uint
UserID uint
User User // 用于声明 Product belongs to User,
gorm.Model
}
type User struct {
gorm.Model
Code string `gorm:"primary_key"`
Name string
}
func main() {
db, err := gorm.Open("postgres", "host=localhost user=testuser dbname=testdb sslmode=disable password=123456")
defer db.Close()
fmt.Println(err)
if err == nil {
db.Debug().AutoMigrate(&User)AutoMigrate(&Product{})
}
}
//CREATE TABLE "products" ("id" serial,"created_at" timestamp with time zone,"updated_at" timestamp with time zone,"deleted_at" timestamp with time zone,"code" text,"price" integer , PRIMARY KEY ("id","code"))
//CREATE INDEX idx_products_deleted_at ON "products"(deleted_at)
我们可以在sql中发现,Product结构体中的成员User并无实际用处,其作用只是用于表示 product belongs to user的关联关系。其次,此次迁移并没有添加任何的外键约束。
然后在main方法中加入这个语句,
user := User{Code: "test", Name: "test"}
db.Debug().Create(&user)
// INSERT INTO "users" ("created_at","updated_at","deleted_at","code","name") VALUES ('2019-04-25 23:40:45','2019-04-25 23:40:45',NULL,'test','test')
db.Debug().Model(&user).Related(&product)
// SELECT * FROM "products" WHERE "products"."deleted_at" IS NULL AND (("user_id" = 5))
Has one
has_one 关联也建立两个模型之间的一对一关系,但语义和结果有点不一样。这种关联表示模型的实例包含或拥有另一个模型的实例。