当前位置:首页 > 文章列表 > Golang > Go问答 > 在 Go 中推广 *sql.Rows 的遍历

在 Go 中推广 *sql.Rows 的遍历

来源:stackoverflow 2024-02-18 20:18:24 0浏览 收藏

从现在开始,我们要努力学习啦!今天我给大家带来《在 Go 中推广 *sql.Rows 的遍历》,感兴趣的朋友请继续看下去吧!下文中的内容我们主要会涉及到等等知识点,如果在阅读本文过程中有遇到不清楚的地方,欢迎留言呀!我们一起讨论,一起学习!

问题内容

我正在使用go开发一个web api,有很多冗余的数据库查询扫描代码。

func (m *contractmodel) workquestions(cid int) ([]models.workquestion, error) {
    results, err := m.db.query(queries.work_questions, cid)
    if err != nil {
        return nil, err
    }

    var workquestions []models.workquestion
    for results.next() {
        var wq models.workquestion
        err = results.scan(&wq.contractstateid, &wq.questionid, &wq.question, &wq.id, &wq.answer, &wq.compulsory)
        if err != nil {
            return nil, err
        }
        workquestions = append(workquestions, wq)
    }

    return workquestions, nil
}

func (m *contractmodel) questions(cid int) ([]models.question, error) {
    results, err := m.db.query(queries.questions, cid)
    if err != nil {
        return nil, err
    }

    var questions []models.question
    for results.next() {
        var q models.question
        err = results.scan(&q.question, &q.answer)
        if err != nil {
            return nil, err
        }
        questions = append(questions, q)
    }

    return questions, nil
}

func (m *contractmodel) documents(cid int) ([]models.document, error) {
    results, err := m.db.query(queries.documents, cid)
    if err != nil {
        return nil, err
    }

    var documents []models.document
    for results.next() {
        var d models.document
        err = results.scan(&d.document, &d.s3region, &d.s3bucket, &d.source)
        if err != nil {
            return nil, err
        }
        documents = append(documents, d)
    }

    return documents, nil
}

我需要概括此代码,以便可以将结果 *sql.rows 传递给函数并获取包含扫描行的结构切片。我知道 sqlx 包中有一个 structscan 方法,但无法使用该方法,因为我有大量使用 go 标准 database/sql 包编写的代码。

使用 reflect 包,我可以创建通用的 structscan 函数,但 reflect 包无法从传递的 interface{} 类型创建结构体切片。我需要实现的目标如下

func RowsToStructs(rows *sql.Rows, model interface{}) ([]interface{}, error) {
    // 1. Create a slice of structs from the passed struct type of model
    // 2. Loop through each row,
    // 3. Create a struct of passed mode interface{} type
    // 4. Scan the row results to a slice of interface{}
    // 5. Set the field values of struct created in step 3 using the slice in step 4
    // 6. Add the struct created in step 3 to slice created in step 1
    // 7. Return the struct slice
}

我似乎找不到一种方法来扫描作为模型参数传递的结构并使用反射包创建它的一个切片。有没有解决方法,或者我是否以错误的方式看待这个问题?

结构体字段具有从结果返回的正确列数和正确的顺序


解决方案


您可以通过将指向目标切片的指针作为参数传递来避免在调用函数中使用类型断言。这是经过修改的 rowstostructs:

// rowstostructs scans rows to the slice pointed to by dest.
// the slice elements must be pointers to structs with exported
// fields corresponding to the the columns in the result set.
//
// the function panics if dest is not as described above.
func rowstostructs(rows *sql.rows, dest interface{}) error {

    // 1. create a slice of structs from the passed struct type of model
    //
    // not needed, the caller passes pointer to destination slice.
    // elem() dereferences the pointer.
    //
    // if you do need to create the slice in this function
    // instead of using the argument, then use
    // destv := reflect.makeslice(reflect.typeof(model).

    destv := reflect.valueof(dest).elem()

    // allocate argument slice once before the loop.

    args := make([]interface{}, destv.type().elem().numfield())

    // 2. loop through each row

    for rows.next() {

        // 3. create a struct of passed mode interface{} type
        rowp := reflect.new(destv.type().elem())
        rowv := rowp.elem()

        // 4. scan the row results to a slice of interface{}
        // 5. set the field values of struct created in step 3 using the slice in step 4
        //
        // scan directly to the struct fields so the database
        // package handles the conversion from database
        // types to a go types.
        //
        // the slice args is filled with pointers to struct fields.

        for i := 0; i < rowv.numfield(); i++ {
            args[i] = rowv.field(i).addr().interface()
        }

        if err := rows.scan(args...); err != nil {
            return err
        }

        // 6. add the struct created in step 3 to slice created in step 1

        destv.set(reflect.append(destv, rowv))

    }
    return nil
}

这样称呼它:

func (m *contractmodel) documents(cid int) ([]*models.document, error) {
    results, err := m.db.query(queries.documents, cid)
    if err != nil {
        return nil, err
    }
    defer results.close()
    var documents []*models.document
    err := rowstostruct(results, &documents)
    return documents, err
}

通过将查询移至辅助函数来消除更多样板:

func querytostructs(dest interface{}, db *sql.db, q string, args ...interface{}) error {
    rows, err := db.query(q, args...)
    if err != nil {
        return err
    }
    defer rows.close()
    return rowstostructs(rows, dest)
}

这样称呼它:

func (m *contractmodel) documents(cid int) ([]*models.document, error) {
    var documents []*model.document
    err := querytostructs(&documents, m.db, queries.documents, cid)
    return documents, err
}

我处理“通用代码”的建议非常简单,主要依赖于这样的前提:检索/获取操作大多数时候用于将这些结果返回到 web 浏览器客户端。因此,这些结果反过来会利用 json 包转换为 json。

文件:db_common.go

// interface for things that performs an scan over a given row.
// actually it is a common interface for https://pkg.go.dev/database/sql#rows.scan and https://pkg.go.dev/database/sql#row.scan
type rowscanner interface {
    scan(dest ...interface{}) error
}

// scans a single row from a given query
type rowscanfunc func(rows rowscanner) (interface{}, error)

// scans multiples rows using a scanner function in order to build a new "scanable" struct
func scanmultiples(rows *sql.rows, rowscanfunc rowscanfunc) ([]interface{}, error) {
    scaneables := []interface{}{}
    for rows.next() {
        scanable, err := rowscanfunc(rows)
        if scanable == nil {
            return nil, err
        }
        scaneables = append(scaneables, scanable)
    }
    err := rows.err()
    if err != nil {
        return nil, err
    }
    return scaneables, nil
}

然后我像这样使用了上面的抽象:

文件:dao/crud_operations.go

// Type that models a row of a given db table
type TableRow struct {
    Id int
    Name string
    // more fields...
}

func GetAll() ([]interface{}, error) {
    rows, err := Db.Query(`
        SELECT id, name
        FROM a_table`
    if err != nil {
        return nil, err
    }
    defer rows.Close()
    return ScanMultiples(rows, scanRow)
}

// Gets an specific legal entity by id
func GetById(id int) (interface{}, error) {
    row := Db.QueryRow(`
        SELECT id, name
        FROM a_table
        WHERE id = $1`, id)
    return scanRow(row)
}


// Scans a row interpreting it as 'TableRow' struct
func scanRow(rows RowScanner) (*TableRow, error) { // DO NOTE that implements the RowScanner interface!
    var tableRow TableRow

    err := rows.Scan(tableRow.Id,tableRow.Name)
    if err != nil {
        return nil, err
    }
    return &tableRow, nil
}
  • 好处是您只需编写一个扫描行的函数即可。
  • 另一个好处是代码不会与外部依赖项纠缠在一起。
  • 另一个好处是代码不依赖 reflect 包或那些复杂的东西。
  • 坏事是您返回了 interface{},并且您可能必须将其转换为原始数据类型,为此必须编写 3 行(在最坏的情况下)。
  • 同样,您不必担心结果是否会像 bytes, err := json.marshal(results) 那样进行封送,因为 json 包接收 interface{} (=any) 作为参数.

以上就是《在 Go 中推广 *sql.Rows 的遍历》的详细内容,更多关于的资料请关注golang学习网公众号!

版本声明
本文转载于:stackoverflow 如有侵犯,请联系study_golang@163.com删除
Golang 中进行依赖注入模拟数据库的单元测试Golang 中进行依赖注入模拟数据库的单元测试
上一篇
Golang 中进行依赖注入模拟数据库的单元测试
设置HTML页面上的滚动条
下一篇
设置HTML页面上的滚动条
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    542次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    508次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    497次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    484次学习
查看更多
AI推荐
  • SEO标题协启动:AI驱动的智能对话与内容生成平台 - 提升创作效率
    协启动
    SEO摘要协启动(XieQiDong Chatbot)是由深圳协启动传媒有限公司运营的AI智能服务平台,提供多模型支持的对话服务、文档处理和图像生成工具,旨在提升用户内容创作与信息处理效率。平台支持订阅制付费,适合个人及企业用户,满足日常聊天、文案生成、学习辅助等需求。
    7次使用
  • Brev AI:零注册门槛的全功能免费AI音乐创作平台
    Brev AI
    探索Brev AI,一个无需注册即可免费使用的AI音乐创作平台,提供多功能工具如音乐生成、去人声、歌词创作等,适用于内容创作、商业配乐和个人创作,满足您的音乐需求。
    7次使用
  • AI音乐实验室:一站式AI音乐创作平台,助力音乐创作
    AI音乐实验室
    AI音乐实验室(https://www.aimusiclab.cn/)是一款专注于AI音乐创作的平台,提供从作曲到分轨的全流程工具,降低音乐创作门槛。免费与付费结合,适用于音乐爱好者、独立音乐人及内容创作者,助力提升创作效率。
    6次使用
  • SEO标题PixPro:AI驱动网页端图像处理平台,提升效率的终极解决方案
    PixPro
    SEO摘要PixPro是一款专注于网页端AI图像处理的平台,提供高效、多功能的图像处理解决方案。通过AI擦除、扩图、抠图、裁切和压缩等功能,PixPro帮助开发者和企业实现“上传即处理”的智能化升级,适用于电商、社交媒体等高频图像处理场景。了解更多PixPro的核心功能和应用案例,提升您的图像处理效率。
    6次使用
  • EasyMusic.ai:零门槛AI音乐生成平台,专业级输出助力全场景创作
    EasyMusic
    EasyMusic.ai是一款面向全场景音乐创作需求的AI音乐生成平台,提供“零门槛创作 专业级输出”的服务。无论你是内容创作者、音乐人、游戏开发者还是教育工作者,都能通过EasyMusic.ai快速生成高品质音乐,满足短视频、游戏、广告、教育等多元需求。平台支持一键生成与深度定制,积累了超10万创作者,生成超100万首音乐作品,用户满意度达99%。
    9次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码