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
  • 1. 說明 /* Performs operations on System.String instances that contain file or directory path information. These operations are performed in a cross-pla ...
  • 視頻地址:【WebApi+Vue3從0到1搭建《許可權管理系統》系列視頻:搭建JWT系統鑒權-嗶哩嗶哩】 https://b23.tv/R6cOcDO qq群:801913255 一、在appsettings.json中設置鑒權屬性 /*jwt鑒權*/ "JwtSetting": { "Issuer" ...
  • 引言 集成測試可在包含應用支持基礎結構(如資料庫、文件系統和網路)的級別上確保應用組件功能正常。 ASP.NET Core 通過將單元測試框架與測試 Web 主機和記憶體中測試伺服器結合使用來支持集成測試。 簡介 集成測試與單元測試相比,能夠在更廣泛的級別上評估應用的組件,確認多個組件一起工作以生成預 ...
  • 在.NET Emit編程中,我們探討了運算操作指令的重要性和應用。這些指令包括各種數學運算、位操作和比較操作,能夠在動態生成的代碼中實現對數據的處理和操作。通過這些指令,開發人員可以靈活地進行算術運算、邏輯運算和比較操作,從而實現各種複雜的演算法和邏輯......本篇之後,將進入第七部分:實戰項目 ...
  • 前言 多表頭表格是一個常見的業務需求,然而WPF中卻沒有預設實現這個功能,得益於WPF強大的控制項模板設計,我們可以通過修改控制項模板的方式自己實現它。 一、需求分析 下圖為一個典型的統計表格,統計1-12月的數據。 此時我們有一個需求,需要將月份按季度劃分,以便能夠直觀地看到季度統計數據,以下為該需求 ...
  • 如何將 ASP.NET Core MVC 項目的視圖分離到另一個項目 在當下這個年代 SPA 已是主流,人們早已忘記了 MVC 以及 Razor 的故事。但是在某些場景下 SSR 還是有意想不到效果。比如某些靜態頁面,比如追求首屏載入速度的時候。最近在項目中回歸傳統效果還是不錯。 有的時候我們希望將 ...
  • System.AggregateException: 發生一個或多個錯誤。 > Microsoft.WebTools.Shared.Exceptions.WebToolsException: 生成失敗。檢查輸出視窗瞭解更多詳細信息。 內部異常堆棧跟蹤的結尾 > (內部異常 #0) Microsoft ...
  • 引言 在上一章節我們實戰了在Asp.Net Core中的項目實戰,這一章節講解一下如何測試Asp.Net Core的中間件。 TestServer 還記得我們在集成測試中提供的TestServer嗎? TestServer 是由 Microsoft.AspNetCore.TestHost 包提供的。 ...
  • 在發現結果為真的WHEN子句時,CASE表達式的真假值判斷會終止,剩餘的WHEN子句會被忽略: CASE WHEN col_1 IN ('a', 'b') THEN '第一' WHEN col_1 IN ('a') THEN '第二' ELSE '其他' END 註意: 統一各分支返回的數據類型. ...
  • 在C#編程世界中,語法的精妙之處往往體現在那些看似微小卻極具影響力的符號與結構之中。其中,“_ =” 這一組合突然出現還真不知道什麼意思。本文將深入剖析“_ =” 的含義、工作原理及其在實際編程中的廣泛應用,揭示其作為C#語法奇兵的重要角色。 一、下劃線 _:神秘的棄元符號 下劃線 _ 在C#中並非 ...