go-xorm使用外鍵報錯(Cannot add or update a child row: a foreign key constraint fails)

問題描述

使用mysql會常常遇到要使用外鍵的場景,go-xorm 做爲一個orm的框架,在數據映射上使用很是方便,但在增添數據是常常碰到報錯:mysql

Cannot add or update a child row: a foreign key constraint failssql

情不知所起數據庫

明明直接在數據庫中插數據並無報錯,爲何用代碼跑就出問題了?框架

報錯緣由

以上報錯信息,英文的主要意思時外鍵約束未被知足,沒法添加數據編輯器

具體緣由插入的數據用外鍵的id在原表中不存在。函數

瞎編的案例

一個不嚴謹的業務場景

假設一個樓房有多個郵箱能夠接受郵件,匿名郵件只有收件地址沒有寄件地址,但每一個樓房都有一個固定的郵遞小哥負責將本樓房發出的郵件送到目的地,要統計樓房之間的通訊狀況須要三個表ui

mysql initail script

create table if no exists `buildings`(
    `id` int(11) unsigned not null auto_inrement,
    `addr` varchar(64) not null unique,
    primary key(`id`)
)ENGINE=InnoDB

create table if no exists `mailboxes`(
    `id` int(11) unsigned not null auto_inrement,
    `number` int(11) not null,
    `building_id` int(11) not null,
    `username` varchar(16),
    primary key(`id`),
    unique key `building_mailbox` (`number`, `building_id`),
    constraint `mail_address` foreign key (`building_id`) refernces `buildings`(`id`) on delete cascade
)ENGINE=InnoDB

create table if no exists `links`(
    `id` int(11) unsigned not null auto_inrement,
    `mialbox_id` int(11) not null,
    `building_id` int(11) not null,
    primary key(`id`),
    unique key `mail_link` (`mialbox_id`, `building_id`),
    constraint `mailbox_link` foreign key (`building_id`) refernces `buildings`(`id`) on delete cascade,
    constraint `building_link` foreign key (`mialbox_id`) refernces `mailboxes`(`id`) on delete cascade
)ENGINE=InnoDB

go-xorm models

須要三個model,兩個輔助modelcode

主要model

type Building struct{
    ID int64 `xorm:"pk autoincr notnull 'id'"`
    Addr string `xorm:"varchar(64) nutnull unique"`
}

type Mailbox struct{
    ···
}

type Link struct{
    ···
}

ps:使用vscode時,編輯器會建議將字段‘Id’改成‘ID’,但字段‘ID’對應數據庫的字段會變成‘i_d’。因此爲了強迫症舒服,必須在後面的xorm裏面標註在數據庫對於的字段,即上文中的‘id’orm

輔助model

type BuildingMailbox struct{
    Building `xorm:"extends"`
    Mailbox `xorm:"extends"`
}

type BuildingMailboxLink struct{

}

Insert

func InsertLink(engine *xorm.Engine,box Mailbox,building Building) (l Link,err error){
    l.MailboxID = box.ID
    l.BuildingID = building.ID
    _,err = engine.Table("links").Insert(&l)
    if err != nil{
        return fmt.Errorf("InsertLink:%v",err)
    }
    return nil
}

錯誤緣由

我遇到,或者說我犯過的錯誤有兩種對象

using update

場景描述

func UpdateMailboxUsername(engine *xorm.Engine,id int64,name string) (box Mailbox,err error){
    box.Username = name
    _,err = engine.Table("mailboxes").ID(id).Update(box)
    return
}

使用上述代碼更新數據庫不會有任何問題。問題出在如下狀況

box,err := UpdateMailboxUsername(e,id,"xorm")
if err !=nil{
    ···deal with the error···
}
link,has,err := GetLinkByBoxAndBuilding(e,box,building)
if err !=nil{
    ···deal with the error···
}
if !has{
    link,err = InsertLink(e,box,building)
}

這個時候InsertLink就會報以上的錯。額且錯誤並非因爲InsertLink。

錯誤解釋

Update方法能夠接受如下兩種狀況

_,err = engine.Table("mailboxes").ID(id).Update(box)

_,err = engine.Table("mailboxes").ID(id).Update(&box)

但兩種狀況都不會修改box的ID值。

ps:使用 Insert(&box) 會在box中注入保存後的id值

內聯查詢結果映射

使用內聯查詢

func GetMailboxByAddrAndNum(e *xorm.Engine,addr string,num int64) (b BuildingMailbox,has bool,err error){
    has,err = engine.Table("mailboxes").Select("mailboxes.*, buildings.*").
        Join("INNER","buildings","mailboxes.building_id = buildings.id").
        Where("mailboxed.number = ?",num).And("buidings.addr = ?",addr).Get(&b)
    if err !=nil{
        err = fmt.Errorf("GetMailboxByAddrAndNum:%v",err)
    }
    return
}

以上函數可以獲得結果,經過使用b.Mailboxb.Building,可以直接使用兩個對象,並且從數值上看好像沒錯,除了同樣——id

進過和數據庫仔細對比會發現,只有id字段會與數據庫記錄的不相同。

錯誤緣由

BuildingMailbox解析出錯

在上述的

···Select("mailboxes.*, buildings.*")···

BuildingMailbox*定義中的順序不一致。其餘字段名稱不一樣解析不會出現錯誤。但因爲mailbox和building都有id字段,在解析式若是輔助結構體中定義的與數據就可能出現由於順序不一樣而致使解析時出現id字段交換的狀況。

小結

本文總結了筆者使用xorm的外鍵時犯過的錯誤。第一種錯誤是由於對go-xorm包不夠熟悉,一段時間後天然就能夠避免;第二種則要隱蔽得多,須要多多注意。

相關文章
相關標籤/搜索