You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

不使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 20:47:39