在 Go 中推广 *sql.Rows 的遍历
来源:stackoverflow
时间:2024-02-18 20:18:24 358浏览 收藏
从现在开始,我们要努力学习啦!今天我给大家带来《在 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学习网公众号!
-
502 收藏
-
502 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
139 收藏
-
204 收藏
-
325 收藏
-
477 收藏
-
486 收藏
-
439 收藏
-
357 收藏
-
352 收藏
-
101 收藏
-
440 收藏
-
212 收藏
-
143 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 508次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习