golang sqlx scan 到结构体中_GOLANG:使用SQLX库执行简单的SQL语句
虽然 GOLANG 自带 SQL 库 database/sql,但为了简化代码提高编程效率,笔者推荐使用第三方库来处理数据库相关操作。
本文介绍如何使用 SQLX 库连接 MySQL 数据库并执行简单的 SELECT 查询语句。
示例代码来自 nwpc-oper/nmc-typhoon-db-client 项目,从 NMC 台风数据库中检索台风数据,计划用于 CMA-PI 上的业务系统。
<h2>安装</h2>连接 MySQL 数据库需要下载数据库驱动 Go-MySQL-Driver。
<pre class="has"><code>go get -u github.com/go-sql-driver/mysql</code></pre>下载 SQLX 库
<pre class="has"><code>go get github.com/jmoiron/sqlx</code></pre>在源码中载入上述两个库
<pre class="has"><code>import (// ...skip... _ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
)
</code></pre> <h2>创建数据库连接</h2>
SQLX 使用的数据库连接地址字符串与 SQLAlchemy 不同,格式如下所示
<pre class="has"><code>user:password@tcp(host:port)/database?param1=value1</code></pre>
为了解析时间字段,增加 <code>parseTime=true</code> 参数
<pre class="has"><code>var db *sqlx.DBconn := fmt.Sprintf(
"%s:%s@tcp(%s)/%s?charset=utf8&parseTime=true&loc=Local",
config.Auth.User,
config.Auth.Password,
config.Host,
config.DatabaseName,
)
db, err = sqlx.Open("mysql", conn)
if err != nil {
log.Fatal("open db connection has error:", err)
return nil, err
}
defer db.Close()
</code></pre> <h2>执行查询</h2>
使用 <code>sqlx.DB</code> 对象的 <code>Queryx()</code> 函数执行 SQL 语句,返回值中的 <code>rows</code> 是一个迭代器。
<pre class="has"><code>querySQL := fmt.Sprintf("SELECT %s FROM %s WHERE %s AND %s",queryColumnsString,
tableName,
datetimeQuery,
forecastHourQuery,
)
rows, err := db.Queryx(querySQL)
if err != nil {
log.Fatal("query db has error:", err)
return nil, err
}
defer rows.Close()
</code></pre> <h2>处理结果</h2>
在标准库 database/sql 中,对每行对象使用 <code>Scan()</code> 方法获取具体的字段值。
该方法需要为每个字段设置一个变量。例如
<pre class="has"><code>var (name string,
number int,
speed float,
)
row.Scan(&name, &number, &speed)
</code></pre>
如果检索结果中字段很多,就可能会导致代码不够简洁。
SQLX 库提供 <code>StructScan()</code> 方法,可以将所有字段放入一个结构体中。
下面首先定义结构体 <code>Record</code> 代表每行数据,再将检索结果全部转为 <code>Record</code>。
<h3>定义记录结构体</h3>为每个字段都创建对应的成员,可以使用名称为 <code>db</code> 的 struct tag,为字段指定对应的列名。
<pre class="has"><code>type Record struct {Xuhao int `csv:"xuhao"`
Center string `csv:"center"`
Bwtype DataString `csv:"bwtype"`
FcstType string `db:"FCSTType" csv:"FCSTType"` Zone DataString `csv:"zone"`
Tfbh DataString `csv:"tfbh"`
Tfbhbabj DataString `csv:"tfbhbabj"`
Engname DataString `csv:"engname"`
Datetime NullDateTime `csv:"datetime"`
Fcsthour int `csv:"fcsthour"`
Lat DataFloat64 `csv:"lat"`
Lon DataFloat64 `csv:"lon"`
Pressure DataInt32 `csv:"pressure"`
Windv DataFloat64 `csv:"windv"`
Gusts DataFloat64 `csv:"gusts"`
Strength DataString `csv:"strength"`
Windclass DataInt32 `csv:"windclass"`
Movedir DataString `csv:"movedir"`
Movespeed DataFloat64 `csv:"movespeed"`
Wind7v1 DataFloat64 `csv:"wind7v1"`
Wind7v2 DataFloat64 `csv:"wind7v2"`
Wind7v3 DataFloat64 `csv:"wind7v3"`
Wind7v4 DataFloat64 `csv:"wind7v4"`
Wind10v1 DataFloat64 `csv:"wind10v1"`
Wind10v2 DataFloat64 `csv:"wind10v2"`
Wind10v3 DataFloat64 `csv:"wind10v3"`
Wind10v4 DataFloat64 `csv:"wind10v4"`
Wind12v1 DataFloat64 `csv:"wind12v1"`
Wind12v2 DataFloat64 `csv:"wind12v2"`
Wind12v3 DataFloat64 `csv:"wind12v3"`
Wind12v4 DataFloat64 `csv:"wind12v4"`
}
</code></pre>
注:上面的代码中使用了自定义的数据类型 <code>DataString</code>,<code>DataFloat64</code>,<code>DataInt32</code> 和 <code>NullDateTime</code>,并定义名为 csv 的 struct tag,这都是为了将结果输出到 CSV 格式文件,后面会介绍。
<h3>解析检索数据</h3>使用 <code>StructScan</code> 方法根据每行结果填充 <code>Record</code> 对象,将所有结果放到 <code>records</code> 数组中。
<pre class="has"><code>records := []Record{}for rows.Next() {
var record Record
err = rows.StructScan(&record)
if err != nil {
log.Fatal("scan row has error:", err)
return nil, err
}
records = append(records, record)
}
err = rows.Err()
if err != nil {
log.Fatal("query rows has error:", err)
return nil, err
}
</code></pre> <h2>输出CSV文件</h2>
使用 csvutil 库将 <code>Records</code> 对象输出到 CSV 文件中
<h3>自定义类型</h3>因为数据库中的部分字段可以为空,所以不能使用内置的格式,而是使用类似 <code>mysql.NullTime</code>,<code>sql.NullString</code> 等特殊类型。
csvutil 库在输出 CSV 文件时会调用对象的 <code>MarshalCSV()</code> 方法,上述这些特殊类型都没有定义该方法,所以无法直接使用。
nmc-typhoon-db-client 项目使用自定义类型封装上述类型,并为这些自定义类型创建 <code>MarshalCSV()</code> 方法。这就是上面提到的 <code>DataString</code>,<code>DataFloat64</code>,<code>DataInt32</code> 和 <code>NullDateTime</code> 几种类型。
以 <code>DataInt32</code> 为例说明。<code>MarshalCSV()</code> 方法会检测是否为空,只有不为空的情况下才会返回字节数组。
<pre class="has"><code>type DataInt32 struct {sql.NullInt32
}
func (d DataInt32) MarshalCSV() ([]byte, error) {
if d.Valid {
return []byte(fmt.Sprintf("%d", d.Int32)), nil
} else {
return nil, nil
}
}
</code></pre>
另外,这种方式也可以用于自定义日期和时间的序列化。
<code>DateTime</code> 是对 <code>time.Time</code> 的封装,序列化时返回 <code>YYYYMMDDHH</code> 格式的字符串
<pre class="has"><code>type DateTime struct {time.Time
}
func (date DateTime) MarshalCSV() ([]byte, error) {
return []byte(date.Format("200601021504")), nil
}
</code></pre> <h3>输出</h3>
<code>csvutil.Marshal()</code> 函数返回序列化后的字节数组,可以直接写入到文件中。
默认设置下,输出的 CSV 文件使用逗号分隔,并包含表头。
<pre class="has"><code>b, err := csvutil.Marshal(records)if err != nil {
fmt.Println("error:", err)
return err
}
f, err := os.Create(filePath)
if err != nil {
fmt.Println("crate file error:", err)
return err
}
f.Write(b)
f.Close()
</code></pre> <h2>讨论</h2>
当前代码还有改进空间,<code>queryColumnsString</code> 字符串由列名字符串数组拼接而成,与 <code>Record</code> 类型的成员变量名称有重复。后续可以考虑使用反射机制,直接从 <code>Record</code> 类型生成 <code>queryColumnsString</code> 字符串。
<h2>参考</h2>nwpc-oper/nmc-typhoon-db-client
https://github.com/nwpc-oper/nmc-typhoon-db-client
《SQLAlchemy:执行简单的SQL语句》介绍使用 Python 的 SQLAlchemy 库实现同样的功能。
题图由 Presentsquare Presentsquare 在 Pixabay 上发布。
您可能感兴趣的文章:
golang sqlx scan 到结构体中_GOLANG:使用SQLX库执行简单的SQL语句
go对接接口
【七天从零实现ORM框架】Day02:database/sql基础
零基础学习Go语言
golang sdk后端怎么用_Golang资深后端工程师需要了解的知识点
SQL2Struct:一款根据sql语句自动生成golang结构体的chrome插件
数据库查询语言(1)
php安全问题思考
JDBCTM 指南:入门4 - Statement
golang 回收机制