如何用sqlx在PostgreSQL存储递归引用的Go结构体?
解决Go递归结构体与PostgreSQL多对多关联的存储问题
哥们,你现在碰到的核心问题是关系型数据库(比如PostgreSQL)没法直接映射Go里的互相递归切片结构体——你原来想在表字段里存数组来保存关联关系,这在SQL的设计逻辑里是不规范的,而且很难维护和查询。咱们得先从数据库表结构的设计入手,再调整代码逻辑。
一、正确的数据库表设计(多对多关联)
Person和Tenant是典型的多对多关系:一个用户可以属于多个租户,一个租户可以有多个用户。这种关系在SQL里需要用中间关联表来实现,而不是在主表里存数组。
修改后的表结构应该是这样的:
CREATE TABLE IF NOT EXISTS person ( id INT PRIMARY KEY, username TEXT NOT NULL UNIQUE ); CREATE TABLE IF NOT EXISTS tenant ( id INT PRIMARY KEY, name TEXT NOT NULL UNIQUE ); -- 中间关联表,存储Person和Tenant的对应关系 CREATE TABLE IF NOT EXISTS person_tenant ( person_id INT REFERENCES person(id) ON DELETE CASCADE, tenant_id INT REFERENCES tenant(id) ON DELETE CASCADE, PRIMARY KEY (person_id, tenant_id) -- 联合主键避免重复关联 );
二、调整Go结构体定义
原来的结构体里的Person []Person和Tenants *[]Tenant是关联关系,不是数据库表的字段,所以要去掉它们的db标签,只保留表本身的字段:
type Tenant struct { Id int `db:"id"` Name string `db:"name"` Persons []Person // 这是关联字段,不需要db标签 } type Person struct { Id int `db:"id"` Username string `db:"username"` Tenants []Tenant // 同样是关联字段,不需要db标签 }
三、用sqlx实现存储与查询
1. 插入数据(带关联关系)
因为是多对多,得先插入Person和Tenant的主记录,再插入中间表的关联记录:
func main() { // 省略数据库连接代码... // 先创建表 var schema = ` CREATE TABLE IF NOT EXISTS person ( id INT PRIMARY KEY, username TEXT NOT NULL UNIQUE ); CREATE TABLE IF NOT EXISTS tenant ( id INT PRIMARY KEY, name TEXT NOT NULL UNIQUE ); CREATE TABLE IF NOT EXISTS person_tenant ( person_id INT REFERENCES person(id) ON DELETE CASCADE, tenant_id INT REFERENCES tenant(id) ON DELETE CASCADE, PRIMARY KEY (person_id, tenant_id) ); ` db.MustExec(schema) // 定义要插入的数据 person1 := Person{Id: 1, Username: "newuser"} tenant1 := Tenant{Id: 1, Name: "newtenant"} // 开启事务 tx := db.MustBegin() // 插入Person tx.NamedExec("INSERT INTO person (id, username) VALUES (:id, :username)", &person1) // 插入Tenant tx.NamedExec("INSERT INTO tenant (id, name) VALUES (:id, :name)", &tenant1) // 插入关联关系 tx.Exec("INSERT INTO person_tenant (person_id, tenant_id) VALUES ($1, $2)", person1.Id, tenant1.Id) // 提交事务 tx.Commit() // 查询数据(带关联) // 先查Tenant var out []Tenant db.Select(&out, "SELECT * FROM tenant ORDER BY name ASC") // 为每个Tenant查询关联的Person for i := range out { var persons []Person db.Select(&persons, ` SELECT p.* FROM person p JOIN person_tenant pt ON p.id = pt.person_id WHERE pt.tenant_id = $1 `, out[i].Id) out[i].Persons = persons } fmt.Printf("Tenants with persons: %+v\n", out) }
2. 查询关联数据的逻辑
这里需要注意:sqlx本身不会自动处理关联关系,所以得手动执行两次查询——先查主表数据,再通过中间表查询关联的子数据,最后填充到结构体里。
四、更省心的替代库建议
如果你觉得手动处理关联关系太麻烦,推荐使用GORM(Go的ORM框架),它对多对多关联的支持非常友好,能自动生成中间表、自动处理关联的插入和查询,不用写大量手动关联的SQL。
用GORM实现的话,只需要给结构体加上关联标签:
type Tenant struct { ID int `gorm:"primaryKey"` Name string Persons []Person `gorm:"many2many:person_tenant;"` // 指定中间表名 } type Person struct { ID int `gorm:"primaryKey"` Username string Tenants []Tenant `gorm:"many2many:person_tenant;"` }
插入和查询会变得非常简单:
// 插入数据 db.Create(&tenant1) db.Model(&tenant1).Association("Persons").Append(&person1) // 查询数据(自动带关联) var tenant Tenant db.Preload("Persons").First(&tenant, 1)
这样就完全不用手动处理中间表的SQL了,GORM会帮你搞定所有关联逻辑。
内容的提问来源于stack exchange,提问作者Battalgazi
相关产品推荐
相关产品推荐

