golang Gorm與數(shù)據(jù)庫(kù)完整性約束詳解
數(shù)據(jù)庫(kù)約束要點(diǎn):
主鍵約束(非空且唯一)外鍵約束 子表外鍵字段的值必須在主表被參照字段值得范圍內(nèi),或者為NULL;外鍵參照的必須是主表的主鍵或唯一鍵;主表主鍵/唯一鍵被子表參照時(shí),主表相應(yīng)記錄不允許被刪除
在golang中,采用orm對(duì)數(shù)據(jù)庫(kù)進(jìn)行建模是比較方便的。grom是其中一個(gè)比較流行的orm工具。
本篇基于golang、grom1.91、和PostgreSQL來(lái)進(jìn)行說(shuō)明。
注:本文的例子是極端情況,一般情況只是單字段主鍵。
1、實(shí)體完整性:
每個(gè)關(guān)系(表)有且僅有一個(gè)主鍵,每一個(gè)主鍵值必須唯一,而且不允許為“空”(NULL)或重復(fù)。
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對(duì)象創(chuàng)建表,其使用的SQL如下:
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;
說(shuō)明:
1.1、grom.Model是gorm預(yù)定義的結(jié)構(gòu),用于實(shí)現(xiàn)軟刪除,定義如下:
type Model struct { ID uint `gorm:"primary_key"` CreatedAt time.Time UpdatedAt time.Time DeletedAt *time.Time `sql:"index"` }
它里面已經(jīng)定義了主鍵,在本例中,我們還定義了一個(gè)主鍵:
Code string `gorm:"primary_key"`
從SQL輸出我們看到:
CONSTRAINT products_pkey PRIMARY KEY (code, id)
因此,Gorm實(shí)現(xiàn)了完全的實(shí)體完整性支持,即可以支持字段主鍵,也可以支持聯(lián)合主鍵。
1.2、對(duì)比結(jié)構(gòu)體和sql語(yǔ)句可以看出
1.2.1 表名=結(jié)構(gòu)體名小寫(xiě)的復(fù)數(shù) 例子:Product變?yōu)?products
1.2.2 字段名=結(jié)構(gòu)體成員名大寫(xiě)分隔的子串小寫(xiě)形式用下劃線連接 例子:ID變?yōu)閕d CreatedAt變?yōu)閏reated_at
1.3、前述1.1和1.2構(gòu)成了Gorm的convention,它的文檔里有,默認(rèn)情況下,就是這么處理,但是用戶可以不用gorm.Model,自定義表名、字段名,都可以支持。
2、域完整性:
是指數(shù)據(jù)庫(kù)表中的列必須滿足某種特定的數(shù)據(jù)類型或約束,又叫用戶定義完整性。包括:字段類型、值域、小數(shù)位數(shù)、CHECK、FOREIGN KEY 約束和DEFAULT、 NOT NULL。它們有的定義在字段上,有的定義在表上。例如:FOREIGN KEY 約束在PostgresSQL中,就是在表級(jí)別定義的;而,字段類型、長(zhǎng)度、小數(shù)位數(shù)就是在字段上定義的。
2.1 通過(guò)結(jié)構(gòu)體tag
`gorm:"xxx"` ,在字段上可以使用:type、size、precision、not null、default,Gorm就可以完成這些域完整性的定義
2.2 FOREIGN KEY 約束
2.2.1 單字段外鍵約束
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文檔,創(chuàng)建了一個(gè)products belongs to user關(guān)系。執(zhí)行的sql是:
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沒(méi)有添加任何約束。按照Gorm文檔,這就是belongs to標(biāo)準(zhǔn)定義。
它不添加外鍵約束。
那么,改為顯式標(biāo)準(zhǔn)的形式,采用foreignkey tag呢?
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"` }
執(zhí)行的sql是:
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"))
也沒(méi)有添加任何外鍵約束。
因此,gorm tag 的 foreignkey 和 association_foreignkey并不會(huì)添加外鍵約束。
但是,我們可以用sql tag來(lái)添加外鍵約束?。。∪缦拢?/p>
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"` }
創(chuàng)建products表的語(yǔ)句:
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"))
注意,當(dāng)使用sql tag時(shí),不像gorm tag,它要你用數(shù)據(jù)庫(kù)表名和字段名,而gorm就只需要你使用結(jié)構(gòu)體和其成員名即可。
外鍵被定義了,此時(shí),可以滿足外鍵約束,如前述,具體是:
子表外鍵字段的值必須在主表被參照字段值得范圍內(nèi),或者為NULL;外鍵參照的必須是主表的主鍵或唯一鍵;主表主鍵/唯一鍵被子表參照時(shí),主表相應(yīng)記錄不允許被刪除
此時(shí)外鍵約束的名字是數(shù)據(jù)庫(kù)自己取的,可能長(zhǎng)了,你可以自定義:
UserID uint `sql:"type:integer constraint ref REFERENCES users(id) on update no action on delete no action"`
加上 constraint xxx,就可以為約束取名為xx了。
上述外鍵約束是在定義結(jié)構(gòu)體時(shí),在結(jié)構(gòu)體成員上定義的,因此翻譯為sql語(yǔ)句就變成了對(duì)字段的外鍵約束,那如果要定義參照聯(lián)合主鍵之類的外鍵呢?就不能在結(jié)構(gòu)體中定義,而要使用gorm的api了。
2.2.2 多字段外鍵約束
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表就有約束:
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了。這里約束的名字就很長(zhǎng)了,api沒(méi)有給你自己取名字的機(jī)會(huì)。
2.3 check約束
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 }
這樣就行??雌饋?lái)這個(gè)check和userID沒(méi)有什么關(guān)系,是的,check會(huì)被定義到表上:
ALTER TABLE public.products
ADD CONSTRAINT products CHECK (code <> ''::text);
因此,Check也完美了,找個(gè)結(jié)構(gòu)體的字段,然后加上check就行了。
3、參照完整性:
對(duì)于永久關(guān)系的相關(guān)表,在更新、插入或刪除記錄時(shí),如果只改其一,就會(huì)影響數(shù)據(jù)的完整性。對(duì)于更新、插入或刪除表間數(shù)據(jù)的完整性,統(tǒng)稱為參照完整性。
對(duì)于外鍵約束,插入?yún)⒄胀暾员粷M足。因此,如前述:
UserID uint `sql:"type:integer REFERENCES users(id) on update no action on delete no action"`
定義好on update 和 on delete的參數(shù),就可以滿足參照完整性。
具體改為:
UserID uint `sql:"type:integer REFERENCES users(id) on update cascade on delete cascade"`
即可,而且數(shù)據(jù)庫(kù)還允許有別的選擇,這里是級(jí)聯(lián)更新和級(jí)聯(lián)刪除,主表已刪除,子表就跟著刪,這是數(shù)據(jù)庫(kù)參照完整性的原初定義。
ps. gorm不默認(rèn)實(shí)施參照完整性,不加約束的原因查了其git issue,主要是因?yàn)閜ostgresql要求被關(guān)聯(lián)的表要先存在。而這會(huì)導(dǎo)致創(chuàng)建表和自動(dòng)升級(jí)表migration的順序依賴,所以用戶要sqltag或者調(diào)用api手動(dòng)實(shí)施。
4、*1對(duì)多 和 多對(duì)多關(guān)系
這不屬于完整性范疇。
4.1 1對(duì)多
1對(duì)多不需要實(shí)施完整性約束,因?yàn)橛脩艨梢詫?duì)應(yīng)0到多個(gè)產(chǎn)品。因此,表結(jié)構(gòu)里無(wú)需添加額外的約束。
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一對(duì)多的典型定義,users表不會(huì)多任何字段,product表會(huì)多user_id字段。這里UserID是外鍵。也可以顯式定義,foreignkey 和Association ForeignKey 上例相當(dāng)于:
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 多對(duì)多
在關(guān)系型數(shù)據(jù)庫(kù)中,多對(duì)多關(guān)系需要多一張表,總共3張表,完整性grom是如何保證的?
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"` }
此時(shí),會(huì)多一個(gè)表(jointtable連接表):
CREATE TABLE "user_language" ("user_id" integer,"product_id" integer, PRIMARY KEY ("user_id","product_id"))
products和users表的主鍵,被聯(lián)合作為新表的主鍵。在新表中,user_id和product_id也是外鍵,在Gorm中,是可以在many2many關(guān)系中自定義外鍵、關(guān)聯(lián)外鍵的。當(dāng)然,外鍵約束就不要想了。
那么,在上例中,按照grom的語(yǔ)法,對(duì)于Products成員,外鍵和關(guān)聯(lián)外鍵分別是什么呢?簡(jiǎn)言之,在gorm所有情況下,將嵌入結(jié)構(gòu)體和其父結(jié)構(gòu)體關(guān)聯(lián)起來(lái)的那個(gè)字段,就是外鍵;關(guān)聯(lián)外鍵是寫(xiě)入外鍵的值的來(lái)源對(duì)應(yīng)的鍵,通常就是父結(jié)構(gòu)體的主鍵。在多對(duì)多情況下,如上例,連接表的user_id是外鍵,而寫(xiě)入時(shí),并沒(méi)有將user_id寫(xiě)入Products []Product,寫(xiě)入的是product_id代表的數(shù)據(jù),因此product_id是associate_foreignkey,這是gorm的約定,很費(fèi)解,解釋也牽強(qiáng)。
下面是多對(duì)多自引用:
type User struct { gorm.Model Friends []*User `gorm:"many2many:friendships;association_jointable_foreignkey:friend_id"` }
用association_jointable_foreignkey在連接表里創(chuàng)建了一個(gè)字段。也比較費(fèi)解。
綜上:
1、字段的基本約束,通過(guò)gorm tag基本都可以設(shè)置。
2、gorm支持實(shí)體完整性約束。
3、域完整性約束中,外鍵約束需要通過(guò) sql tag或調(diào)用api實(shí)現(xiàn),check約束可以直接在字段上定義。
4、參照完整性gorm不能默認(rèn)實(shí)現(xiàn),必須通過(guò)sql tag或者調(diào)用api實(shí)現(xiàn)。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教。
相關(guān)文章
Go語(yǔ)言基礎(chǔ)知識(shí)總結(jié)(語(yǔ)法、變量、數(shù)值類型、表達(dá)式、控制結(jié)構(gòu)等)
這篇文章主要介紹了Go語(yǔ)言基礎(chǔ)知識(shí)總結(jié)(語(yǔ)法、變量、數(shù)值類型、表達(dá)式、控制結(jié)構(gòu)等),本文匯總了Go語(yǔ)言的入門(mén)知識(shí),需要的朋友可以參考下2014-10-10go語(yǔ)言定時(shí)器Timer及Ticker的功能使用示例詳解
這篇文章主要為大家介紹了go語(yǔ)言定時(shí)器的功能使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04GoLang channel底層代碼實(shí)現(xiàn)詳解
Channel和goroutine的結(jié)合是Go并發(fā)編程的大殺器。而Channel的實(shí)際應(yīng)用也經(jīng)常讓人眼前一亮,通過(guò)與select,cancel,timer等結(jié)合,它能實(shí)現(xiàn)各種各樣的功能。接下來(lái),我們就要梳理一下GoLang channel底層代碼實(shí)現(xiàn)2022-10-10Go?對(duì)多個(gè)網(wǎng)絡(luò)命令空間中的端口進(jìn)行監(jiān)聽(tīng)的解決方案
這篇文章主要介紹了Go?如何對(duì)多個(gè)網(wǎng)絡(luò)命令空間中的端口進(jìn)行監(jiān)聽(tīng),本文給大家介紹的非常詳細(xì),需要的朋友可以參考下2024-07-07