Go語言SQL語句到結構體的轉換命令行工具

来源:https://www.cnblogs.com/kphang/archive/2022/11/22/16916783.html
-Advertisement-
Play Games

學習:SQL 語句到結構體的轉換 | Go 語言編程之旅 (eddycjy.com) 目標:SQL表轉換為Go語言結構體 可以線上體驗這個過程:SQL生成GO語言結構體 - 支持批量處理 (tl.beer) MySQL資料庫中的表結構,本質上是SQL語句。 CREATE TABLE `USER`( ...


學習: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資料庫,並查詢下表。

image-20221122155923274

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語言結構體;數據源: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"

    測試,大概如下:

    image-20221122203503619

  • 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"
}

定義結構體

最終的結構體模板:

這個結構體是最終轉化後,在終端輸出的結構體格式化字元串;

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"
}

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 最近需要接手別人c#那邊組的一個項目新增頁面,但他們的是React的框架,作為一名後端,沒接觸過,一臉懵逼。。。。。。 說哈我的處理思路: 一、先用相應的程式打開該項目的源碼。如:react用vscode打開 二、先找到了頁面,查看頁面結構 這是我後面加的頁面,可以看出來,less類似css樣式 j ...
  • Express 快速創建 Web 伺服器 express 的基本使用 先安裝express包 npm i [email protected] 1.導入 express const express = require('express'); 2.創建 web 伺服器 const app = express( ...
  • 最近用 antd pro 開發了一些 web 小工具。 antd pro 不僅僅是升級版的 antd 組件,更重要的是提供了全套的前端解決方案,包括前端工程的編譯打包,路由配置,數據管理,樣式和資源的引用,和後端的交互方式。 甚至對於網站的國際化也有支持。 本篇是近期使用antd pro 時,用到的 ...
  • 大家好,EluxJS是一套基於“微模塊”和“模型驅動”的跨平臺、跨框架『同構方案』,歡迎瞭解... 可怕的巨石怪 工作中最可怕的是什麼?是遇到業務複雜且亂作一團的巨石應用。改一發而動全身,無法漸進式重構,也沒人敢對歷史包袱進行優化,欠下的代碼債只能像滾雪球一樣越積越多,終於到某天玩不下去,大佬選擇了 ...
  • 傳統大企業更喜歡私有化部署、個性化交付的傳統模式,因為他們需要更強的管控和更高的安全性。 然而,中小企業付費能力有限,需求往往也更加標準化,所以更喜歡價格更低的、訂購更簡單的SaaS產品。 為了滿足不同客戶的需求,多租戶的底層架構設計是至關重要的。 ...
  • 3. Drools入門案例 全套代碼及資料全部完整提供,點此處下載 本小節通過一個Drools入門案例來讓大家初步瞭解Drools的使用方式、對Drools有一個整體概念。 3.1 業務場景說明 業務場景:消費者在圖書商城購買圖書,下單後需要在支付頁面顯示訂單優惠後的價格。具體優惠規則如下: | 規 ...
  • 我們之前有<C++模板編程模塊>中的第<四>節 理解空間配置器allocator優化STL中的Vector 我將在此基礎上加入迭代器功能代碼 Iterator 為什麼可以遍歷所有的容器的方式都一樣? auto it =continer.beign(); for( ;it!=continer.end( ...
  • 最小生成樹(貪心演算法) 概念 一個有 n 個結點的連通圖的生成樹是原圖的極小連通子圖,且包含原圖中的所有 n 個結點,並且有保持圖連通的最少的邊。 連通圖有多種連接方式,而其中最小的連通圖,就是最小生成樹 連通圖分為:無向、有向 無向連通圖:所以頂點相連,但各個邊都沒有方向 有向連通圖:邊有方向 1 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...