不使用ORM将一对多关联绑定到Golang结构体的方法
不使用ORM实现Go结构体与数据库一对多查询结果绑定(适配MySQL/PostgreSQL/MariaDB)
核心思路
JOIN查询返回的是用户+单条帖子的扁平化结果,我们需要通过一个map缓存已处理的用户,将同一用户的多条帖子合并到其Posts切片中,避免重复创建User实例。
MySQL实现方案
1. 依赖准备
使用标准库database/sql搭配MySQL驱动(github.com/go-sql-driver/mysql),先初始化数据库连接:
import ( "database/sql" "fmt" _ "github.com/go-sql-driver/mysql" ) func main() { // 替换为你的数据库连接信息 db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/dbname?parseTime=true") if err != nil { panic(err) } defer db.Close() // 验证连接可用性 if err := db.Ping(); err != nil { panic(err) } // 后续查询逻辑... }
2. 执行查询并绑定结果
遍历查询结果时,用map[int]*User缓存用户,将相同ID的用户的帖子合并:
type ( User struct { ID int Name string Posts []*Post } Post struct { ID int Title string } ) func main() { // 省略数据库连接代码... rows, err := db.Query(` SELECT u.id, u.name, p.id, p.title FROM users AS u JOIN posts AS p ON u.id = p.user_id; `) if err != nil { panic(err) } defer rows.Close() userMap := make(map[int]*User) for rows.Next() { var ( userID int userName string postID int postTitle string ) // 严格按照查询字段顺序扫描数据 if err := rows.Scan(&userID, &userName, &postID, &postTitle); err != nil { panic(err) } // 检查用户是否已在缓存中 user, exists := userMap[userID] if !exists { user = &User{ ID: userID, Name: userName, Posts: []*Post{}, } userMap[userID] = user } // 将当前帖子添加到用户的Posts切片 user.Posts = append(user.Posts, &Post{ ID: postID, Title: postTitle, }) } // 处理遍历过程中可能出现的错误 if err := rows.Err(); err != nil { panic(err) } // 按需将map转换为切片(方便后续遍历) users := make([]*User, 0, len(userMap)) for _, u := range userMap { users = append(users, u) } // 打印结果验证 for _, u := range users { fmt.Printf("User ID: %d, Name: %s\n", u.ID, u.Name) for _, p := range u.Posts { fmt.Printf(" Post ID: %d, Title: %s\n", p.ID, p.Title) } } }
PostgreSQL/MariaDB适配方案
PostgreSQL
逻辑和MySQL完全一致,仅需替换驱动为github.com/lib/pq,并修改数据库连接字符串:
import _ "github.com/lib/pq" // PostgreSQL连接字符串示例 db, err := sql.Open("postgres", "user=user password=password dbname=dbname host=127.0.0.1 port=5432 sslmode=disable")
MariaDB
MariaDB与MySQL协议完全兼容,直接使用MySQL驱动即可,连接字符串格式和MySQL一致,无需修改核心逻辑代码。
注意事项
- 确保查询语句的字段顺序与
rows.Scan的参数顺序完全匹配,否则会出现数据绑定错位。 - 必须处理
rows.Next()遍历后的rows.Err(),避免遗漏查询过程中的隐性错误。 - 如果需要按特定顺序返回用户,可以在查询时添加
ORDER BY u.id,或者在转换为切片后手动排序。
内容的提问来源于stack exchange,提问作者Kohei Yasan
相关产品推荐
相关产品推荐

