问题描述
使用mysql会经常遇到要使用外键的场景,go-xorm 作为一个orm的框架,在数据映射上使用非常方便,但在增添数据是经常碰到报错:
Cannot add or update a child row: a foreign key constraint fails
情不知所起
明明直接在数据库中插数据并没有报错,为什么用代码跑就出问题了?
报错原因
以上报错信息,英文的主要意思时外键约束未被满足,无法添加数据
具体原因插入的数据用外键的id在原表中不存在。
瞎编的案例
一个不严谨的业务场景
假设一个楼房有多个邮箱可以接受邮件,匿名邮件只有收件地址没有寄件地址,但每个楼房都有一个固定的邮递小哥负责将本楼房发出的邮件送到目的地,要统计楼房之间的通信情况需要三个表
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,两个辅助model
主要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’
辅助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.Mailbox和b.Building,能够直接使用两个对象,而且从数值上看好像没错,除了一样——id
进过和数据库仔细对比会发现,只有id字段会与数据库记录的不相同。
错误原因
BuildingMailbox解析出错
在上述的
···Select("mailboxes.*, buildings.*")···
和BuildingMailbox*定义中的顺序不一致。其他字段名称不同解析不会出现错误。但由于mailbox和building都有id字段,在解析式如果辅助结构体中定义的与数据就可能出现因为顺序不同而导致解析时出现id字段交换的情况。
小结
本文总结了笔者使用xorm的外键时犯过的错误。第一种错误是因为对go-xorm包不够熟悉,一段时间后自然就可以避免;第二种则要隐蔽得多,需要多多注意。