golang 操作数据库,不使用 ORM 的情况下,查询数据一般使用 Scan。
网上能查到的例子,大多数是先定义一个实体,然后再通过结果集的 Scan,放到结构体的字段中。
这样做的局限性就是不通用,不同的表的查询需要定义不同的结构体,然后写不同的 Scan。
于是考虑有没有一种通用的方式,把取出来的结果集放到 Map 切片中。
终于功夫不负有心人,做了一次代码的搬运工。
本文相关代码:
hellogo/database at main · bettersun/hellogo (github.com)
golang查询数据库的常用写法
type Book struct {
BookId int
Title string
Author string
PublishDate string
附赠建表SQL:
CREATE TABLE m_book
book_id int8 NOT NULL,
title text NOT NULL,
author text NULL,
total_page int4 NULL,
publish_date date NULL,
CONSTRAINT book_pkey PRIMARY KEY (book_id)
网上能找到的常用写法:
func RetreiveBook(db *sql.DB) ([]Book, error) {
var books []Book
sql := `select book_id, title, author, to_char(publish_date, 'YYYY/MM/DD') as publish_date from m_book`
rows, err := db.Query(sql)
if err != nil {
log.Println(err)
return books, err
defer rows.Close()
for rows.Next() {
var book Book
rows.Scan(&book.BookId, &book.Title, &book.Author, &book.PublishDate)
books = append(books, book)
return books, nil
这种写法的优点非常高内聚,针对单个表的查询 SQL 语句,和获取数据的过程,都写在函数里,对外只返回结构体的切片,查询完成后使用结构体变量处理复杂业务也比较方便。
缺点也很明显。一个是不同的表需要定义不同的结构体,然后写不同的 SQL 语句,然后Scan的参数也不同;另一个是如果要修改查询的字段个数,那 Scan 的参数也需要对应修改。
如果查询的表或业务比较少,可以每个表和业务都定义相关的结构体,然后写相应的查询函数。
如果要查询的表比较多,就会觉得比较重复。
如果只是查询出来的内容返回个Map,然后在前端使用的话,那更没有必要定义一堆结构体,写一堆查询函数。直接写一个通用的查询函数,查询的结果集输出为Map,就能省去很多代码。
SQL查询结果集输出到Map切片
一开始试着看 golang 的操作数据库的源码,看看有没有什么方式能实现。
发现白搭,脑子不灵光,根本看不进去,还是得依靠强大的网络。
最终实现如下:
func RetrieveMap(db *sql.DB, sSql string) ([]map[string]interface{}, error) {
stmt, err := db.Prepare(sSql)
if err != nil {
log.Println(err)
return nil, err
defer stmt.Close()
rows, err := stmt.Query()
if err != nil {
log.Println(err)
return nil, err
defer rows.Close()
columns, err := rows.Columns()
if err != nil {
log.Println(err)
return nil, err
count := len(columns)
mData := make([]map[string]interface{}, 0)
values := make([]interface{}, count)
valPointers := make([]interface{}, count)
for rows.Next() {
for i := 0; i < count; i++ {
valPointers[i] = &values[i]
rows.Scan(valPointers...)
entry := make(map[string]interface{})
for i, col := range columns {
var v interface{}
val := values[i]
b, ok := val.([]byte)
if ok {
v = string(b)
} else {
v = val
entry[col] = v
mData = append(mData, entry)
return mData, nil
上述代码参考:
stackoverflow.com/questions/1…
主要逻辑没改,原代码直接转成了 JSON 字符串,另外个人主要是为了理解加了注释。
只需要在调用函数前定义好查询用的 SQL,而且查询的字段数增加或减少都无需修改函数,比单个表分别写查询函数的方式方便很多。
结果使用 Map 切片接收,Map 切片可以直接返回给前端使用。
结果使用 Map 切片接收也是双刃剑,查询后的业务处理复杂的情况下,使用 Map 会比较麻烦。
调用前创建 DB 连接,然后定义好查询用的 SQL。
这里使用的是 postgre 数据库。
import (
"database/sql"
"log"
"testing"
_ "github.com/lib/pq"
func TestRetrieveMap(t *testing.T) {
db, err := sql.Open("postgres", "host=127.0.0.1 port=5432 user=postgres password=12 dbname=postgres sslmode=disable")
defer db.Close()
if err != nil {
log.Fatalln("无法连接数据库")
sql := "select book_id, title, author, to_char(publish_date, 'YYYY/MM/DD') as publish_date from m_book"
m, err := RetrieveMap(db, sql)
if err != nil {
log.Println(err)
log.Printf("%v", m)
查询结果例:
=== RUN TestRetrieveMap
2021/11/05 12:02:06 [map[author:司马迁 book_id:1 publish_date:2020/12/12 title:史记] map[author:当年明月 book_id:2 publish_date:2020/09/12 title:明朝那些事儿]]
--- PASS: TestRetrieveMap (0.03s)
复制代码