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包不够熟悉,一段时间后天然就能够避免;第二种则要隐蔽得多,须要多多注意。

相关文章
相关标签/搜索