快速生成 Go 語言數據庫模型:探索 GORM gen 的便捷神器

一、安裝 mysql

1、安裝 mysql

# 拉取 MySQL 鏡像
docker pull mysql
# 運行 MySQL 容器
docker run --name mysql-server -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 -d mysql
# 安裝mysql客戶端
brew install mysql-client 
vi .zshrc 或 .bash_profile
export PATH=$PATH:/usr/local/opt/mysql-client/bin/mysql
source .zshrc 
# 連接到 MySQL 服務器
mysql -h 127.0.0.1 -P 3306 -u root -p123456
# 啓停命令
docker stop mysql-server
docker rm mysql-server

2、創建數據庫和表

create database test charset utf8;
use test;
CREATE TABLE book
(
    `id`     bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主鍵',
    `title`  varchar(128) NOT NULL COMMENT '書籍名稱',
    `author` varchar(128) NOT NULL COMMENT '作者',
    `price`  int NOT NULL DEFAULT '0' COMMENT '價格',
    `publish_date` datetime COMMENT '出版日期',
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='書籍表';

二、Gen 基本使用

1、gen_demo/cmd/gen/generate.go

package main
// gorm gen configure
import (
  "fmt"
  "gen_demo/dal/model"
  "gorm.io/driver/mysql"
  "gorm.io/gorm"
  "gorm.io/gen"
)
const MySQLDSN = "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True"
func connectDB(dsn string) *gorm.DB {
  db, err := gorm.Open(mysql.Open(dsn))
  if err != nil {
    panic(fmt.Errorf("connect db fail: %w", err))
  }
  return db
}
func main() {
  // 指定生成代碼的具體相對目錄(相對當前文件),默認爲:./query
  // 默認生成需要使用WithContext之後纔可以查詢的代碼,但可以通過設置gen.WithoutContext禁用該模式
  g := gen.NewGenerator(gen.Config{
    // 默認會在 OutPath 目錄生成CRUD代碼,並且同目錄下生成 model 包
    // 所以OutPath最終package不能設置爲model,在有數據庫表同步的情況下會產生衝突
    // 若一定要使用可以通過ModelPkgPath單獨指定model package的名稱
    OutPath: "../../dal/query",
    /* ModelPkgPath: "dal/model"*/
    // gen.WithoutContext:禁用WithContext模式
    // gen.WithDefaultQuery:生成一個全局Query對象Q
    // gen.WithQueryInterface:生成Query接口
    Mode: gen.WithDefaultQuery | gen.WithQueryInterface,
  })
  // 通常複用項目中已有的SQL連接配置db(*gorm.DB)
  // 非必需,但如果需要複用連接時的gorm.Config或需要連接數據庫同步表信息則必須設置
  g.UseDB(connectDB(MySQLDSN))
  // 從連接的數據庫爲所有表生成Model結構體和CRUD代碼
  // 也可以手動指定需要生成代碼的數據表
  g.ApplyBasic(g.GenerateAllTable()...)
  // 執行並生成代碼
  g.Execute()
}
cd cmd/gen
go run generate.go
├── cmd
│   └── gen
│       └── generate.go
├── dal
│   ├── model
│   │   └── book.gen.go
│   └── query
│       ├── book.gen.go
│       └── gen.go
├── go.mod
├── go.sum
└── main.go

2、gen_demo/dal/db.go

package dal
import (
  "fmt"
  "gorm.io/driver/mysql"
  "gorm.io/gorm"
)
var DB *gorm.DB
func ConnectDB(dsn string) *gorm.DB {
  db, err := gorm.Open(mysql.Open(dsn))
  if err != nil {
    panic(fmt.Errorf("connect db fail: %w", err))
  }
  return db
}

3、main.go

package main
import (
  "context"
  "fmt"
  "gen_demo/dal"
  "gen_demo/dal/model"
  "gen_demo/dal/query"
  "time"
)
// gen demo
// MySQLDSN MySQL data source name
const MySQLDSN = "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True"
func init() {
  dal.DB = dal.ConnectDB(MySQLDSN).Debug()
}
func main() {
  // 設置默認DB對象
  query.SetDefault(dal.DB)
  //Create()
  //Query()
  //GetDefine()
  //GetFilter()
  GetSearch()
}
func Create() {
  // 創建
  b1 := model.Book{
    Title:       "《七米的Go語言之路》",
    Author:      "七米",
    PublishDate: time.Date(2023, 11, 15, 0, 0, 0, 0, time.UTC),
    Price:       100,
  }
  err := query.Book.WithContext(context.Background()).Create(&b1)
  if err != nil {
    fmt.Printf("create book fail, err:%v\n", err)
    return
  }
}
func Query() {
  // 查詢
  //book, err := query.Book.WithContext(context.Background()).First()
  // 也可以使用全局Q對象查詢
  book, err := query.Q.Book.WithContext(context.Background()).First()
  if err != nil {
    fmt.Printf("query book fail, err:%v\n", err)
    return
  }
  fmt.Printf("book:%v\n", book)
}
func Update() {
  // 更新
  ret, err := query.Book.WithContext(context.Background()).
    Where(query.Book.ID.Eq(1)).
    Update(query.Book.Price, 200)
  if err != nil {
    fmt.Printf("update book fail, err:%v\n", err)
    return
  }
  fmt.Printf("RowsAffected:%v\n", ret.RowsAffected)
}
func Del() {
  // 刪除
  ret, err := query.Book.WithContext(context.Background()).Where(query.Book.ID.Eq(1)).Delete()
  if err != nil {
    fmt.Printf("delete book fail, err:%v\n", err)
    return
  }
  fmt.Printf("RowsAffected:%v\n", ret.RowsAffected)
}

三、自定義 SQL 查詢

1、語法說明

註釋語法

返回結果

1、gen_demo/dal/model/querier.go

// dal/model/querier.go
package model
import "gorm.io/gen"
// 通過添加註釋生成自定義方法
type Querier interface {
  // SELECT * FROM @@table WHERE id=@id
  GetByID(id int) (gen.T, error) // 返回結構體和error
  // GetByIDReturnMap 根據ID查詢返回map
  //
  // SELECT * FROM @@table WHERE id=@id
  GetByIDReturnMap(id int) (gen.M, error) // 返回 map 和 error
  // SELECT * FROM @@table WHERE author=@author
  GetBooksByAuthor(author string) ([]*gen.T, error) // 返回數據切片和 error
}

2、gen_demo/cmd/gen/generate.go

  // 通過ApplyInterface添加爲book表添加自定義方法
  g.ApplyInterface(func(model.Querier) {}, g.GenerateModel("book"))
cd cmd/gen
go run generate.go

3、main.go

func GetDefine() {
  // 使用自定義的GetBooksByAuthor方法
  rets, err := query.Book.WithContext(context.Background()).GetBooksByAuthor("七米")
  if err != nil {
    fmt.Printf("GetBooksByAuthor fail, err:%v\n", err)
    return
  }
  for i, b := range rets {
    fmt.Printf("%d:%v\n", i, b)
  }
}

四、模版佔位符

1、gen_demo/dal/model/querier.go

// Filter 自定義Filter接口
type Filter interface {
  // SELECT * FROM @@table WHERE @@column=@value
  FilterWithColumn(column string, value string) (gen.T, error)
}

2、gen_demo/cmd/gen/generate.go

// 爲`Book`添加 `Filter`接口
g.ApplyInterface(func(model.Filter) {}, g.GenerateModel("book"))

3、main.go

func GetFilter() {
  // 使用自定義的GetBooksByAuthor方法
  rets, err := query.Book.WithContext(context.Background()).FilterWithColumn("author", "七米")
  if err != nil {
    fmt.Printf("GetBooksByAuthor fail, err:%v\n", err)
    return
  }
  fmt.Println(rets)
}

五、模版表達式

Gen 爲動態條件 SQL 提供了強大的表達式支持,目前支持以下表達式:

1、gen_demo/dal/model/querier.go

// Searcher 自定義接口
type Searcher interface {
  // Search 根據指定條件查詢書籍
  //
  // SELECT * FROM book
  // WHERE publish_date is not null
  // {{if book != nil}}
  //   {{if book.ID > 0}}
  //     AND id = @book.ID
  //   {{else if book.Author != ""}}
  //     AND author=@book.Author
  //   {{end}}
  // {{end}}
  Search(book *gen.T) ([]*gen.T, error)
}

2、gen_demo/cmd/gen/generate.go

// 通過ApplyInterface添加爲book表添加Searcher接口
g.ApplyInterface(func(model.Searcher) {}, g.GenerateModel("book"))

3、main.go

func GetSearch() {
  b := &model.Book{Author: "張三"}
  rets, err := query.Book.WithContext(context.Background()).Search(b)
  if err != nil {
    fmt.Printf("Search fail, err:%v\n", err)
    return
  }
  for i, b := range rets {
    fmt.Printf("%d:%v\n", i, b)
  }
}
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/ncVMc_Bg5SjKj6W31eq9hA