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
  • 在C#中使用SQL Server實現事務的ACID(原子性、一致性、隔離性、持久性)屬性和使用資料庫鎖(悲觀鎖和樂觀鎖)時,你可以通過ADO.NET的SqlConnection和SqlTransaction類來實現。下麵是一些示例和概念說明。 實現ACID事務 ACID屬性是事務處理的四個基本特征, ...
  • 我們在《SqlSugar開發框架》中,Winform界面開發部分往往也用到了自定義的用戶控制項,對應一些特殊的界面或者常用到的一些局部界面內容,我們可以使用自定義的用戶控制項來提高界面的統一性,同時也增強了使用的便利性。如我們Winform界面中用到的分頁控制項、附件顯示內容、以及一些公司、部門、菜單的下... ...
  • 在本篇教程中,我們學習瞭如何在 Taurus.MVC WebMVC 中進行數據綁定操作。我們還學習瞭如何使用 ${屬性名稱} CMS 語法來綁定頁面上的元素與 Model 中的屬性。通過這些步驟,我們成功實現了一個簡單的數據綁定示例。 ...
  • 是在MVVM中用來傳遞消息的一種方式。它是在MVVMLight框架中提供的一個實現了IMessenger介面的類,可以用來在ViewModel之間、ViewModel和View之間傳遞消息。 Send 接受一個泛型參數,表示要發送的消息內容。 Register 方法用於註冊某個對象接收消息。 pub ...
  • 概述:在WPF中,通過EventHandler可實現基礎和高級的UI更新方式。基礎用法涉及在類中定義事件,併在UI中訂閱以執行更新操作。高級用法藉助Dispatcher類,確保在非UI線程上執行操作後,通過UI線程更新界面。這兩種方法提供了靈活而可靠的UI更新機制。 在WPF(Windows Pre ...
  • 概述:本文介紹了在C#程式開發中如何利用自定義擴展方法測量代碼執行時間。通過使用簡單的Action委托,開發者可以輕鬆獲取代碼塊的執行時間,幫助優化性能、驗證演算法效率以及監控系統性能。這種通用方法提供了一種便捷而有效的方式,有助於提高開發效率和代碼質量。 在軟體開發中,瞭解代碼執行時間是優化程式性能 ...
  • 概述:Cron表達式是一種強大的定時任務調度工具,通過配置不同欄位實現靈活的時間規定。在.NET中,Quartz庫提供了簡便的方式配置Cron表達式,實現精準的定時任務調度。這種靈活性和可擴展性使得開發者能夠根據需求輕鬆地制定和管理定時任務,例如每天備份系統日誌或其他重要操作。 Cron表達式詳解 ...
  • 概述:.NET提供多種定時器,如System.Windows.Forms.Timer適用於UI,System.Web.UI.Timer用於Web,System.Diagnostics.Timer用於性能監控,System.Threading.Timer和System.Timers.Timer用於一般 ...
  • 問題背景 有同事聯繫我說,在生產環境上,訪問不了我負責的common服務,然後我去檢查common服務的health endpoint, 沒問題,然後我問了下異常,timeout導致的System.OperationCanceledException。那大概率是客戶端的問題,會不會是埠耗盡,用ne ...
  • 前言: 在本篇 Taurus.MVC WebMVC 入門開發教程的第四篇文章中, 我們將學習如何實現數據列表的綁定,通過使用 List<Model> 來展示多個數據項。 我們將繼續使用 Taurus.Mvc 命名空間,同時探討如何在視圖中綁定並顯示一個 Model 列表。 步驟1:創建 Model ...