脚本之家,脚本语言编程技术及教程分享平台!
分类导航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服务器之家 - 脚本之家 - Golang - golang Gorm与数据库完整性约束详解

golang Gorm与数据库完整性约束详解

2021-03-13 00:59edl7878 Golang

这篇文章主要介绍了golang Gorm与数据库完整性约束详解,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

数据库约束要点:

主键约束(非空且唯一)外键约束 子表外键字段的值必须在主表被参照字段值得范围内,或者为NULL;外键参照的必须是主表的主键或唯一键;主表主键/唯一键被子表参照时,主表相应记录不允许被删除

golang中,采用orm对数据库进行建模是比较方便的。grom是其中一个比较流行的orm工具。

本篇基于golang、grom1.91、和PostgreSQL来进行说明。

注:本文的例子是极端情况,一般情况只是单字段主键。

1、实体完整性:

每个关系(表)有且仅有一个主键,每一个主键值必须唯一,而且不允许为“空”(NULL)或重复。

?
1
2
3
4
5
6
7
8
type Product struct {
Code string `gorm:"primary_key"`
Price uint
UserID uint //`sql:"type:bigint REFERENCES users(id) on update no action on delete cascade"`
UserCode string //`sql:"type:bigint REFERENCES users(code) on update no action on delete cascade"`
User User //`gorm:"foreignkey:UserID;association_foreignkey:ID"`
gorm.Model
}

在利用gorm的db对象创建表,其使用的SQL如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
CREATE TABLE public.products
(
 code text COLLATE pg_catalog."default" NOT NULL,
 price integer,
 user_id integer,
 user_code text COLLATE pg_catalog."default",
 id integer NOT NULL DEFAULT nextval('products_id_seq'::regclass),
 created_at timestamp with time zone,
 updated_at timestamp with time zone,
 deleted_at timestamp with time zone,
 CONSTRAINT products_pkey PRIMARY KEY (code, id)
)
WITH (
 OIDS = FALSE
)
TABLESPACE pg_default;
ALTER TABLE public.products
 OWNER to postgres;
 
-- Index: idx_products_deleted_at
 
-- DROP INDEX public.idx_products_deleted_at;
 
CREATE INDEX idx_products_deleted_at
 ON public.products USING btree
 (deleted_at)
 TABLESPACE pg_default;

说明:

1.1、grom.Model是gorm预定义的结构,用于实现软删除,定义如下:

?
1
2
3
4
5
6
type Model struct {
 ID uint `gorm:"primary_key"`
 CreatedAt time.Time
 UpdatedAt time.Time
 DeletedAt *time.Time `sql:"index"`
}

它里面已经定义了主键,在本例中,我们还定义了一个主键:

Code string `gorm:"primary_key"`

从SQL输出我们看到:

CONSTRAINT products_pkey PRIMARY KEY (code, id)

因此,Gorm实现了完全的实体完整性支持,即可以支持字段主键,也可以支持联合主键。

1.2、对比结构体和sql语句可以看出

1.2.1 表名=结构体名小写的复数 例子:Product变为 products

1.2.2 字段名=结构体成员名大写分隔的子串小写形式用下划线连接 例子:ID变为id CreatedAt变为created_at

1.3、前述1.1和1.2构成了Gorm的convention,它的文档里有,默认情况下,就是这么处理,但是用户可以不用gorm.Model,自定义表名、字段名,都可以支持。

2、域完整性:

是指数据库表中的列必须满足某种特定的数据类型或约束,又叫用户定义完整性。包括:字段类型、值域、小数位数、CHECK、FOREIGN KEY 约束和DEFAULT、 NOT NULL。它们有的定义在字段上,有的定义在表上。例如:FOREIGN KEY 约束在PostgresSQL中,就是在表级别定义的;而,字段类型、长度、小数位数就是在字段上定义的。

2.1 通过结构体tag

`gorm:"xxx"` ,在字段上可以使用:type、size、precision、not null、default,Gorm就可以完成这些域完整性的定义

2.2 FOREIGN KEY 约束

2.2.1 单字段外键约束

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
type Product struct {
 Code string //`gorm:"primary_key"`
 Price uint
 UserID uint //`sql:"type:bigint REFERENCES users(id) on update no action on delete cascade"`
  //UserCode string //`sql:"type:bigint REFERENCES users(code) on update no action on delete cascade"`
 User User //`gorm:"foreignkey:UserID;association_foreignkey:ID"`
 gorm.Model
}
type User struct {
  //Code string `gorm:"primary_key"`
 Name string
 gorm.Model
  //Product Product //`gorm:"EMBEDDED"`
}

上面的代码按照gorm文档,创建了一个products belongs to user关系。执行的sql是:

?
1
2
CREATE TABLE "users" ("code" text,"name" text,"id" serial,"created_at" timestamp with time zone,"updated_at" timestamp with time zone,"deleted_at" timestamp with time zone , PRIMARY KEY ("code","id"))
CREATE TABLE "users" ("code" text,"name" text,"id" serial,"created_at" timestamp with time zone,"updated_at" timestamp with time zone,"deleted_at" timestamp with time zone , PRIMARY KEY ("code","id"))

我们看到,gorm没有添加任何约束。按照Gorm文档,这就是belongs to标准定义。

它不添加外键约束。

那么,改为显式标准的形式,采用foreignkey tag呢?

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type Product struct {
 Code string //`gorm:"primary_key"`
 Price uint
 UserID uint //`sql:"type:integer REFERENCES users(id)`// on update no action on delete cascade"`
  //UserCode string //`sql:"type:bigint REFERENCES users(code) on update no action on delete cascade"`
  //UserID uint
 User User `gorm:"foreignkey:UserID;association_foreignkey:ID"` //`gorm:"foreignkey:UserID;association_foreignkey:ID"`
 gorm.Model
}
type User struct {
  //Code string `gorm:"primary_key"`
 Name string
 gorm.Model
  //Product Product //`gorm:"EMBEDDED"`
}

执行的sql是:

?
1
2
CREATE TABLE "users" ("name" text,"id" serial,"created_at" timestamp with time zone,"updated_at" timestamp with time zone,"deleted_at" timestamp with time zone , PRIMARY KEY ("id"))
CREATE TABLE "products" ("code" text,"price" integer,"user_id" integer,"id" serial,"created_at" timestamp with time zone,"updated_at" timestamp with time zone,"deleted_at" timestamp with time zone , PRIMARY KEY ("id"))

也没有添加任何外键约束。

因此,gorm tag 的 foreignkey 和 association_foreignkey并不会添加外键约束。

但是,我们可以用sql tag来添加外键约束!!!如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type Product struct {
 Code string //`gorm:"primary_key"`
 Price uint
 UserID uint `sql:"type:integer REFERENCES users(id) on update no action on delete no action"`
  //UserCode string //`sql:"type:bigint REFERENCES users(code) on update no action on delete cascade"`
  //UserID uint
 User User `gorm:"foreignkey:UserID;association_foreignkey:ID"` //`gorm:"foreignkey:UserID;association_foreignkey:ID"`
 gorm.Model
}
type User struct {
  //Code string `gorm:"primary_key"`
 Name string
 gorm.Model
  //Product Product //`gorm:"EMBEDDED"`
}

创建products表的语句:

?
1
CREATE TABLE "products" ("code" text,"price" integer,"user_id" integer REFERENCES users(id) on update no action on delete no action,"id" serial,"created_at" timestamp with time zone,"updated_at" timestamp with time zone,"deleted_at" timestamp with time zone , PRIMARY KEY ("id"))

注意,当使用sql tag时,不像gorm tag,它要你用数据库表名和字段名,而gorm就只需要你使用结构体和其成员名即可。

外键被定义了,此时,可以满足外键约束,如前述,具体是:

子表外键字段的值必须在主表被参照字段值得范围内,或者为NULL;外键参照的必须是主表的主键或唯一键;主表主键/唯一键被子表参照时,主表相应记录不允许被删除

此时外键约束的名字是数据库自己取的,可能长了,你可以自定义:

UserID uint `sql:"type:integer constraint ref REFERENCES users(id) on update no action on delete no action"`

加上 constraint xxx,就可以为约束取名为xx了。

上述外键约束是在定义结构体时,在结构体成员上定义的,因此翻译为sql语句就变成了对字段的外键约束,那如果要定义参照联合主键之类的外键呢?就不能在结构体中定义,而要使用gorm的api了。

2.2.2 多字段外键约束

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type Product struct {
 Code string //`gorm:"primary_key"`
 Price uint
  //UserID uint `sql:"type:integer REFERENCES users(id) on update no action on delete no action"`
  //UserCode string //`sql:"type:bigint REFERENCES users(code) on update no action on delete cascade"`
 UserCode string
 UserID uint
 User User //`gorm:"foreignkey:UserID;association_foreignkey:ID"`//`gorm:"foreignkey:UserID;association_foreignkey:ID"`
 gorm.Model
}
type User struct {
 Code string `gorm:"primary_key"`
 Name string
 gorm.Model
  //Product Product //`gorm:"EMBEDDED"`
}

在程序中使用:

postgres. Model( &Product{}). AddForeignKey( "user_id,user_code", "users(id,code)", "no action", "no action")

这样,products表就有约束:

?
1
2
3
4
CONSTRAINT products_user_id_user_code_users_id_code_foreign FOREIGN KEY (user_code, user_id)
REFERENCES public.users (code, id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION

如此就OK了。这里约束的名字就很长了,api没有给你自己取名字的机会。

2.3 check约束

?
1
2
3
4
5
6
7
8
9
10
type Product struct {
 Code string //`gorm:"primary_key"`
 Price uint
 UserID uint `sql:"type:integer check(code!='')"`
 UserCode string //`sql:"type:bigint constraint ref REFERENCES users(code) on update no action on delete cascade"`
  //UserCode string
  //UserID uint
 User User //`gorm:"foreignkey:UserID;association_foreignkey:ID"`//`gorm:"foreignkey:UserID;association_foreignkey:ID"`
 gorm.Model
}

这样就行。看起来这个check和userID没有什么关系,是的,check会被定义到表上:

ALTER TABLE public.products

ADD CONSTRAINT products CHECK (code <> ''::text);

因此,Check也完美了,找个结构体的字段,然后加上check就行了。

3、参照完整性:

对于永久关系的相关表,在更新、插入或删除记录时,如果只改其一,就会影响数据的完整性。对于更新、插入或删除表间数据的完整性,统称为参照完整性。

对于外键约束,插入参照完整性被满足。因此,如前述:

UserID uint `sql:"type:integer REFERENCES users(id) on update no action on delete no action"`

定义好on update 和 on delete的参数,就可以满足参照完整性。

具体改为:

UserID uint `sql:"type:integer REFERENCES users(id) on update cascade on delete cascade"`

即可,而且数据库还允许有别的选择,这里是级联更新和级联删除,主表已删除,子表就跟着删,这是数据库参照完整性的原初定义。

ps. gorm不默认实施参照完整性,不加约束的原因查了其git issue,主要是因为postgresql要求被关联的表要先存在。而这会导致创建表和自动升级表migration的顺序依赖,所以用户要sqltag或者调用api手动实施。

4、*1对多 和 多对多关系

这不属于完整性范畴。

4.1 1对多

1对多不需要实施完整性约束,因为用户可以对应0到多个产品。因此,表结构里无需添加额外的约束。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type Product struct {
 Code string //`gorm:"primary_key"`
 Price uint
  //UserID uint `sql:"type:integer constraint ref REFERENCES users(id) on update no action on delete no action check(code!='')"`
  //UserCode string //`sql:"type:bigint constraint ref REFERENCES users(code) on update no action on delete cascade"`
  //UserCode string
  //UserID uint
  //User User //`gorm:"foreignkey:UserID;association_foreignkey:ID"`//`gorm:"foreignkey:UserID;association_foreignkey:ID"`
 gorm.Model
 UserID uint
}
type User struct {
 Code string //`gorm:"primary_key"`
 Name string
 gorm.Model
 Products []Product
  //Product Product //`gorm:"EMBEDDED"`
}

上面是gorm一对多的典型定义,users表不会多任何字段,product表会多user_id字段。这里UserID是外键。也可以显式定义,foreignkey 和Association ForeignKey 上例相当于:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type Product struct {
 Code string //`gorm:"primary_key"`
 Price uint
  //UserID uint `sql:"type:integer constraint ref REFERENCES users(id) on update no action on delete no action check(code!='')"`
  //UserCode string //`sql:"type:bigint constraint ref REFERENCES users(code) on update no action on delete cascade"`
  //UserCode string
  //UserID uint
  //User User //`gorm:"foreignkey:UserID;association_foreignkey:ID"`//`gorm:"foreignkey:UserID;association_foreignkey:ID"`
 gorm.Model
 UserID uint
}
type User struct {
 Code string //`gorm:"primary_key"`
 Name string
 gorm.Model
 Products []Product `gorm:"foreignkey:UserID"`
  //Product Product //`gorm:"EMBEDDED"`
}

4.2 多对多

在关系型数据库中,多对多关系需要多一张表,总共3张表,完整性grom是如何保证的?

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type Product struct {
 Code string //`gorm:"primary_key"`
 Price uint
  //UserID uint `sql:"type:integer constraint ref REFERENCES users(id) on update no action on delete no action check(code!='')"`
  //UserCode string //`sql:"type:bigint constraint ref REFERENCES users(code) on update no action on delete cascade"`
  //UserCode string
  //UserID uint
  //User User //`gorm:"foreignkey:UserID;association_foreignkey:ID"`//`gorm:"foreignkey:UserID;association_foreignkey:ID"`
 gorm.Model
  //UserID uint
}
type User struct {
 Code string //`gorm:"primary_key"`
 Name string
 gorm.Model
 Products []Product `gorm:"many2many:user_language"`
  //Product Product //`gorm:"EMBEDDED"`
}

此时,会多一个表(jointtable连接表):

CREATE TABLE "user_language" ("user_id" integer,"product_id" integer, PRIMARY KEY ("user_id","product_id"))

products和users表的主键,被联合作为新表的主键。在新表中,user_id和product_id也是外键,在Gorm中,是可以在many2many关系中自定义外键、关联外键的。当然,外键约束就不要想了。

那么,在上例中,按照grom的语法,对于Products成员,外键和关联外键分别是什么呢?简言之,在gorm所有情况下,将嵌入结构体和其父结构体关联起来的那个字段,就是外键;关联外键是写入外键的值的来源对应的键,通常就是父结构体的主键。在多对多情况下,如上例,连接表的user_id是外键,而写入时,并没有将user_id写入Products []Product,写入的是product_id代表的数据,因此product_id是associate_foreignkey,这是gorm的约定,很费解,解释也牵强。

下面是多对多自引用:

?
1
2
3
4
type User struct {
 gorm.Model
 Friends []*User `gorm:"many2many:friendships;association_jointable_foreignkey:friend_id"`
}

用association_jointable_foreignkey在连接表里创建了一个字段。也比较费解。

综上:

1、字段的基本约束,通过gorm tag基本都可以设置。

2、gorm支持实体完整性约束。

3、域完整性约束中,外键约束需要通过 sql tag或调用api实现,check约束可以直接在字段上定义。

4、参照完整性gorm不能默认实现,必须通过sql tag或者调用api实现。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持服务器之家。如有错误或未考虑完全的地方,望不吝赐教。

原文链接:https://blog.csdn.net/edl7878/article/details/80945777

延伸 · 阅读

精彩推荐
  • GolangGolang中Bit数组的实现方式

    Golang中Bit数组的实现方式

    这篇文章主要介绍了Golang中Bit数组的实现方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    天易独尊11682021-06-09
  • GolangGolang通脉之数据类型详情

    Golang通脉之数据类型详情

    这篇文章主要介绍了Golang通脉之数据类型,在编程语言中标识符就是定义的具有某种意义的词,比如变量名、常量名、函数名等等,Go语言中标识符允许由...

    4272021-11-24
  • Golanggolang json.Marshal 特殊html字符被转义的解决方法

    golang json.Marshal 特殊html字符被转义的解决方法

    今天小编就为大家分享一篇golang json.Marshal 特殊html字符被转义的解决方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧 ...

    李浩的life12792020-05-27
  • Golanggolang的httpserver优雅重启方法详解

    golang的httpserver优雅重启方法详解

    这篇文章主要给大家介绍了关于golang的httpserver优雅重启的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,...

    helight2992020-05-14
  • Golanggolang如何使用struct的tag属性的详细介绍

    golang如何使用struct的tag属性的详细介绍

    这篇文章主要介绍了golang如何使用struct的tag属性的详细介绍,从例子说起,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看...

    Go语言中文网11352020-05-21
  • Golanggo日志系统logrus显示文件和行号的操作

    go日志系统logrus显示文件和行号的操作

    这篇文章主要介绍了go日志系统logrus显示文件和行号的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    SmallQinYan12302021-02-02
  • Golanggo语言制作端口扫描器

    go语言制作端口扫描器

    本文给大家分享的是使用go语言编写的TCP端口扫描器,可以选择IP范围,扫描的端口,以及多线程,有需要的小伙伴可以参考下。 ...

    脚本之家3642020-04-25
  • Golanggolang 通过ssh代理连接mysql的操作

    golang 通过ssh代理连接mysql的操作

    这篇文章主要介绍了golang 通过ssh代理连接mysql的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    a165861639710342021-03-08