当前位置:首页 > 文章列表 > 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推荐
  • 茅茅虫AIGC检测:精准识别AI生成内容,保障学术诚信
    茅茅虫AIGC检测
    茅茅虫AIGC检测,湖南茅茅虫科技有限公司倾力打造,运用NLP技术精准识别AI生成文本,提供论文、专著等学术文本的AIGC检测服务。支持多种格式,生成可视化报告,保障您的学术诚信和内容质量。
    96次使用
  • 赛林匹克平台:科技赛事聚合,赋能AI、算力、量子计算创新
    赛林匹克平台(Challympics)
    探索赛林匹克平台Challympics,一个聚焦人工智能、算力算法、量子计算等前沿技术的赛事聚合平台。连接产学研用,助力科技创新与产业升级。
    101次使用
  • SEO  笔格AIPPT:AI智能PPT制作,免费生成,高效演示
    笔格AIPPT
    SEO 笔格AIPPT是135编辑器推出的AI智能PPT制作平台,依托DeepSeek大模型,实现智能大纲生成、一键PPT生成、AI文字优化、图像生成等功能。免费试用,提升PPT制作效率,适用于商务演示、教育培训等多种场景。
    108次使用
  • 稿定PPT:在线AI演示设计,高效PPT制作工具
    稿定PPT
    告别PPT制作难题!稿定PPT提供海量模板、AI智能生成、在线协作,助您轻松制作专业演示文稿。职场办公、教育学习、企业服务全覆盖,降本增效,释放创意!
    102次使用
  • Suno苏诺中文版:AI音乐创作平台,人人都是音乐家
    Suno苏诺中文版
    探索Suno苏诺中文版,一款颠覆传统音乐创作的AI平台。无需专业技能,轻松创作个性化音乐。智能词曲生成、风格迁移、海量音效,释放您的音乐灵感!
    102次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码