学习:SQL 语句到结构体的转换 | Go 语言编程之旅 (eddycjy.com)
目标:SQL表转换为Go语言结构体
可以在线体验这个过程:SQL生成GO语言结构体 - 支持批量处理 (tl.beer)
MySQL数据库中的表结构,本质上是SQL语句。
CREATE TABLE `USER`(
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT \'primary key\',
`ip_address` INT NOT NULL DEFAULT 0 COMMENT \'ip_address\',
`nickname` VARCHAR(128) NOT NULL DEFAULT \'\' COMMENT \'user note\',
`description` VARCHAR(256) NOT NULL DEFAULT \'\' COMMENT \'user description\',
`creator_email` VARCHAR(64) NOT NULL DEFAULT \'\' COMMENT \'creator email\',
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT \'create time\',
`deleted_at` TIMESTAMP NULL DEFAULT NULL COMMENT \'delete time\',
PRIMARY KEY(`id`)
)ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT=\'user table\';
大概目标就是要把此Table转换为Go语言结构体语句,
type USER struct {
Id uint `comment:\"primary key\"`
IpAddress int `comment:\"ip_address\"`
Nickname string `comment:\"user note\"`
Description string `comment:\"user description\"`
CreatorEmail string `comment:\"creator email\"`
CreatedAt time.Time `comment:\"create time\"`
DeletedAt time.Time `comment:\"delete time\"`
}
结构体变量后面的是`结构体标签 `:Go系列:结构体标签 - 掘金 (juejin.cn)
数据源:MySQL中的information_schema库中有个COLUMNS表,里面记录了mysql所有库中所有表的字段信息。
text/template简要应用
package main
import (
\"os\"
\"strings\"
\"text/template\"
)
const templateText = `
Output 0: {{title .Name1}}
Output 1: {{title .Name2}}
Output 2: {{.Name3 | title}}
`
func main1() {
funcMap := template.FuncMap{\"title\": strings.Title} // type FuncMap map[string]any FuncMap类型定义了函数名字符串到函数的映射
tpl := template.New(\"go-programing-tour\") //创建一个名为\"...\"的模板。
tpl, _ = tpl.Funcs(funcMap).Parse(templateText)
data := map[string]string{
\"Name1\": \"go\",
\"Name2\": \"programing\",
\"Name3\": \"tour\",
}
_ = tpl.Execute(os.Stdout, data)
}
-
模板内内嵌的语法支持,全部需要加
{{ }}
来标记; -
{{.}}
表示当前作用域内的当前对象 data (_ = tpl.Execute(os.Stdout, data)
),Execute()
方法执行的时候,会将{{.Name1}}
替换成data.Name1
; -
在模板中调用函数,
{{函数名 传入参数}}
,因为在模版中,传入参数一般都是string类型; -
template.FuncMap
创建自定义函数,在模板作用域中生效:funcMap := template.FuncMap{\"title\": strings.Title}
作用域中的title,就意味着调用此函数,把后面的作为参数传入函数中;
-
{{.Name3 | title}}
在模板中,会把管道符前面的运算结果作为参数传递给管道符后面的函数; -
more:[译]Golang template 小抄 (colobu.com)
database/sql简要应用
用Go语言链接MySQL数据库,并查询下表。
package main
import (
\"database/sql\"
\"fmt\"
_ \"github.com/go-sql-driver/mysql\" //导入包但不使用,init()
)
func main() {
// DB不是连接,并且只有当需要使用时才会创建连接;
DB, err := sql.Open(\"mysql\", \"user:password@tcp(127.0.0.1:3306)/DBName\")
if err != nil {
fmt.Printf(\"DB:%v invalid,err:%v\\n\", DB, err)
return
}
// defer DB.Close()
// It is rare to Close a DB, as the DB handle is meant to be long-lived and shared between many goroutines.
// Closing a DB is useful if you don\'t plan to use the database again. It does all the cleanup that would be done at program termination but allows the program to continue to run.
// 如果想立即验证连接,需要用Ping()方法;
if err = DB.Ping(); err != nil {
fmt.Println(\"open database fail\")
return
}
fmt.Println(\"connnect success\")
// 读取DB
var (
id int
areacode string
cityname string
citypinyin string
)
// db.Query()表示向数据库发送一个query
rows, err := DB.Query(\"SELECT * FROM businesscities;\")
if err != nil {
fmt.Printf(\"DB.Query:%v invalid,err:%v\\n\", rows, err)
}
if rows == nil {
fmt.Println(\"没有数据\")
}
defer rows.Close() // 很重要;
for rows.Next() {
err := rows.Scan(&id, &areacode, &cityname, &citypinyin)
if err != nil {
fmt.Println(err)
}
fmt.Println(id, areacode, cityname, citypinyin)
}
// 遍历完成后检查error;
err = rows.Err()
if err != nil {
fmt.Println(err)
}
}
DB, err := sql.Open(\"mysql\", \"user:password@tcp(127.0.0.1:3306)/DBName\")
sql.Open
的第一个参数是driver名称,其他的driver还有如sqlite3等;- 第二个参数是driver连接数据库的信息;
- DB不是连接,并且只有当需要使用时才会创建连接;
- sql.DB的设计就是用来作为长连接使用的。不要频繁Open, Close。比较好的做法是,为每个不同的datastore建一个DB对象,保持这些对象Open。另外,sql.Open()的Close()可有可无的原因:
- 官方说明文档:
It is rare to Close a DB, as the DB handle is meant to be long-lived and shared between many goroutines.
- Closing a DB is useful if you don\'t plan to use the database again. It does all the cleanup that would be done at program termination but allows the program to continue to run.
- 官方说明文档:
- 如果想立即验证连接,需要用Ping()方法;
DB.Ping()
rows, err := DB.Query(\"SELECT * FROM businesscities;\")
: db.Query()表示向数据库发送一个query代码;- 对于rows来说,
defer rows.Close()
非常重要 - 遍历rows使用
rows.Next()
, 把遍历到的数据存入变量使用rows.Scan()
- 遍历完成后再检查下是否有error,rows.Err()
搭建子命令“架子”
本文不再赘述子命令的”架子“
- Go语言单词格式转换命令行工具 - KpHang - 博客园 (cnblogs.com)
- Go语言便捷的时间命令行工具 - KpHang - 博客园 (cnblogs.com)
目标:把某个数据库内的某一个表转换为Go语言结构体;数据源:MySQL中的information_schema库中有个COLUMNS表,里面记录了mysql所有库中所有表的字段信息;想一想需要什么功能函数?
- 与数据库建立链接;
- 数据库查询,获取想要的信息;
- 解析查询结果,转换为结构体字符串,输出;
功能函数放在internal包中,不对外公布;
├── internal
│ ├── sql2struct
│ │ ├── mysql.go
│ │ └── template.go
链接数据库并查询
在internal/sql2struct/mysql.go
中。
定义结构体
面向对象编程,要思考需要定义那些结构体。
// 整个数据库连接的核心对象;
type DBModel struct {
DBEngine *sql.DB
DBInfo *DBInfo
}
// 连接MySQL的一些基本信息;
type DBInfo struct {
DBType string
Host string
Username string
Password string
Charset string
}
// TableColumn用来存放COLUMNS表中我们需要的一些字段;
type TableColumn struct {
ColumnName string
DataType string
IsNullable string
ColumnKey string
ColumnType string
ColumnComment string
}
- DBModel:整个数据库连接的核心对象,包括DB主体,DBInfo;
- DBInfo:数据库链接信息,用此信息链接数据库,赋值给DBEngin;
链接数据库前,先创建DBModel核心对象:
func NewDBModel(info *DBInfo) *DBModel {
return &DBModel{DBInfo: info}
}
链接数据库
// (m *DBModel) 有两个东西,此函数是获取第一个东西 DBEngine *sql.DB
func (m *DBModel) Connect() error {
var err error
s := \"%s:%s@tcp(%s)/information_schema?\" +
\"charset=%s&parseTime=True&loc=Local\"
dsn := fmt.Sprintf( // dsn dataSourceName
s,
m.DBInfo.Username,
m.DBInfo.Password,
m.DBInfo.Host,
m.DBInfo.Charset,
)
m.DBEngine, err = sql.Open(m.DBInfo.DBType, dsn)
// 第一个参数为驱动名称,eg mysql;
// 第二个参数为驱动连接数据库的连接信息;dsn dataSourceName
if err != nil {
return err
}
return nil
}
m.DBEngine, err = sql.Open(m.DBInfo.DBType, dsn)
数据库查询
func (m *DBModel) GetColumns(dbName, tableName string) ([]*TableColumn, error) {
query := \"SELECT COLUMN_NAME, DATA_TYPE, COLUMN_KEY, \" +
\"IS_NULLABLE, COLUMN_TYPE, COLUMN_COMMENT \" +
\"FROM COLUMNS WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? \"
// SELECT COLUMN_NAME, DATA_TYPE, COLUMN_KEY, IS_NULLABLE, COLUMN_TYPE, COLUMN_COMMENT FROM COLUMNS WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
rows, err := m.DBEngine.Query(query, dbName, tableName)
// SELECT COLUMN_NAME, DATA_TYPE, COLUMN_KEY, IS_NULLABLE, COLUMN_TYPE, COLUMN_COMMENT FROM COLUMNS WHERE TABLE_SCHEMA = \"dbName\" AND TABLE_NAME = \"tableName\"
if err != nil {
return nil, err
}
if rows == nil {
return nil, errors.New(\"没有数据\")
}
defer rows.Close()
var columns []*TableColumn
for rows.Next() {
var column TableColumn
err := rows.Scan(&column.ColumnName, &column.DataType,
&column.ColumnKey, &column.IsNullable, &column.ColumnType, &column.ColumnComment)
if err != nil {
return nil, err
}
columns = append(columns, &column)
}
return columns, nil
}
-
rows, err := m.DBEngine.Query(query, dbName, tableName)
,Query会把query中的?
,替换成后面的参数,以字符串的形式;SELECT COLUMN_NAME, DATA_TYPE, COLUMN_KEY, IS_NULLABLE, COLUMN_TYPE, COLUMN_COMMENT FROM COLUMNS WHERE TABLE_SCHEMA = \"dbName\" AND TABLE_NAME = \"tableName\"
测试,大概如下:
-
用
rows.Next()
和rows.Scan()
遍历查询结果,每一列信息都放在一个TableColumn结构体中,最终返回一个包括所有列的[]*TableColumn
;
转换为结构体模板
将上面查询返回的[]*TableColumn
,转化为结构体模板,最终输出结构如下:
$ go run ./main.go sql struct --username user --password password --db=dbName --table=tableName
# Output:
type Businesscities struct {
// areacode
Areacode string `json:"areacode"`
// cityname
Cityname string `json:"cityname"`
// citypinyin
Citypinyin string `json:"citypinyin"`
// id
Id int32 `json:"id"`
}
func (model Businesscities) TableName() string {
return \"businesscities\"
}
- ` ` 是结构体标签;Go系列:结构体标签 - 掘金 (juejin.cn)
定义结构体
最终的结构体模板:
这个结构体是最终转化后,在终端输出的结构体格式化字符串;
const strcutTpl = `type {{.TableName | ToCamelCase}} struct {
{{range .Columns}} {{ $length := len .Comment}} {{ if gt $length 0 }}// {{.Comment}} {{else}}// {{.Name}} {{ end }}
{{ $typeLen := len .Type }} {{ if gt $typeLen 0 }}{{.Name | ToCamelCase}} {{.Type}} {{.Tag}}{{ else }}{{.Name}}{{ end }}
{{end}}}
func (model {{.TableName | ToCamelCase}}) TableName() string {
return \"{{.TableName}}\"
}`
// 结构体模板对象;
type StructTemplate struct {
structTpl string
}
func NewStructTemplate() *StructTemplate {
return &StructTemplate{structTpl: strcutTpl}
}
数据表的某一列信息,转换为如下格式:
// 存储转化后的Go结构体对象;
type StructColumn struct {
Name string
Type string
Tag string
Comment string
}
模板渲染用的数据对象:
// 用来存储最终用于渲染的模版对象信息;
type StructTemplateDB struct {
TableName string
Columns []*StructColumn
}
- TableName -> 结构体名字;
- Columns -> 结构体内的变量;
模板渲染前的数据处理
上面的数据库查询,获取到了一个[]*TableColumn
,要把此数据,转换为[]*StructColumn
:
func (t *StructTemplate) AssemblyColumns(tbColumns []*TableColumn) []*StructColumn {
tplColumns := make([]*StructColumn, 0, len(tbColumns))
for _, column := range tbColumns {
tag := fmt.Sprintf(\"`\"+\"json:\"+\"\\\"%s\\\"\"+\"`\", column.ColumnName)
tplColumns = append(tplColumns, &StructColumn{
Name: column.ColumnName,
Type: DBTypeToStructType[column.DataType],
Tag: tag,
Comment: column.ColumnComment,
})
}
return tplColumns
}
-
[]*StructColumn
每一个元素,最终转化为输出结构体模板中的一个成员变量; -
// DataType字段的类型与Go结构体中的类型不是完全一致的; var DBTypeToStructType = map[string]string{ \"int\": \"int32\", \"tinyint\": \"int8\", \"smallint\": \"int\", \"mediumint\": \"int64\", ...
渲染模板
- 模板:structTpl
- 用到的数据:
[]*StructColumn
func (t *StructTemplate) Generate(tableName string, tplColumns []*StructColumn) error {
tpl := template.Must(template.New(\"sql2struct\").Funcs(template.FuncMap{
\"ToCamelCase\": word.UnderscoreToUpperCamelCase, // 大驼峰
}).Parse(t.structTpl))
tplDB := StructTemplateDB{
TableName: tableName,
Columns: tplColumns,
}
err := tpl.Execute(os.Stdout, tplDB)
if err != nil {
return err
}
return nil
}
-
template包使用详情:[译]Golang template 小抄 (colobu.com)
-
在
tpl.Execute(os.Stdout, tplDB)
后,对structTpl解析:const strcutTpl = `type {{.TableName | ToCamelCase}} struct { {{range .Columns}} {{ $length := len .Comment}} {{ if gt $length 0 }}// {{.Comment}} {{else}}// {{.Name}} {{ end }} {{ $typeLen := len .Type }} {{ if gt $typeLen 0 }}{{.Name | ToCamelCase}} {{.Type}} {{.Tag}}{{ else }}{{.Name}}{{ end }} {{end}}} func (model {{.TableName | ToCamelCase}}) TableName() string { return \"{{.TableName}}\" }`
-
// 遍历切片(tplDB.Columns) {{range .Columns}} {{end}}}
-
// 设置结构体成员变量的注释; // 定义变量length,if length > 0 注释用comment,else 注释用Name; {{ $length := len .Comment}} {{ if gt $length 0 }} // {{.Comment}} {{else}}// {{.Name}} {{ end }}
-
// 设置结构体成员变量; // type字符串长度 大于0,就正常设置大驼峰Name,类型,Tag;else 只设置Name; {{ $typeLen := len .Type }} {{ if gt $typeLen 0 }}{{.Name | ToCamelCase}} {{.Type}} {{.Tag}}{{ else }}{{.Name}}{{ end }}
-
sql子命令测试
$ go run ./main.go sql struct --username user --password password --db=dbName --table=tableName
# Output:
type TableName struct {
// areacode
Areacode string `json:"areacode"`
// cityname
Cityname string `json:"cityname"`
// citypinyin
Citypinyin string `json:"citypinyin"`
// id
Id int32 `json:"id"`
}
func (model Businesscities) TableName() string {
return \"businesscities\"
}
- 此处有个问题:go语言编程之旅 sql语句到结构体的转换,\"号打印出 &,#,3,4,; 问题 - Go语言中文网 - Golang中文社区 (studygolang.com)
来源:https://www.cnblogs.com/kphang/p/16916783.html
本站部分图文来源于网络,如有侵权请联系删除。