Go開源世界主流成熟ORM框架gorm實踐分享

2023-05-17 06:00:43

@

概述

定義

GORM 官網地址 https://gorm.io/ 最新版本v1.25.1

GORM 官網檔案地址 https://gorm.io/docs/

GORM 原始碼地址 https://github.com/go-gorm/gorm

GORM 是Golang語言中一個功能齊全的優秀的ORM 框架,對開發者友好,支援多種資料庫,並提供了豐富的功能和 API,可以讓開發者更加方便地進行資料庫操作。

核心功能

  • ORM功能豐富、完整
  • 關聯 (Has One,Has Many,Belongs To,Many To Many,多型,單表繼承)
  • Create,Save,Update,Delete,Find 中勾點方法
  • 支援 PreloadJoins 的預載入
  • 事務,巢狀事務,Save Point,Rollback To Saved Point
  • Context、預編譯模式、DryRun 模式
  • 批次插入,FindInBatches,Find/Create with Map,使用 SQL 表示式、Context Valuer 進行 CRUD
  • SQL 構建器,Upsert,資料庫鎖,Optimizer/Index/Comment Hint,命名引數,子查詢
  • 複合主鍵,索引,約束
  • Auto Migration
  • 自定義 Logger
  • 靈活的可延伸外掛 API:Database Resolver(多資料庫,讀寫分離)、Prometheus…
  • 每個特性都經過了測試的重重考驗
  • 開發者友好

宣告模型與約定

模型是標準的 struct,由 Go 的基本資料型別、實現了 Scanner和 Valuer介面的自定義型別及其指標或別名組成,範例如:

type User struct {
  ID           uint
  Name         string
  Email        *string
  Age          uint8
  Birthday     *time.Time
  MemberNumber sql.NullString
  ActivatedAt  sql.NullTime
  CreatedAt    time.Time
  UpdatedAt    time.Time
}

GORM 傾向於約定優於設定 預設情況下,GORM 使用 ID 作為主鍵,使用結構體名的 蛇形複數 作為表名,欄位名的 蛇形 作為列名,並使用 CreatedAtUpdatedAt 欄位追蹤建立、更新時間。如果遵循GORM採用的約定,則只需編寫很少的設定/程式碼。如果約定不符合需求,GORM也允許指定設定。

gorm.Model

GORM定義了gorm.Model,其中包括欄位ID, CreatedAt, UpdatedAt, DeletedAt,可以將其嵌入到結構中以包含這些欄位

欄位級許可權

可匯出的欄位在使用 GORM 進行 CRUD 時擁有全部的許可權,此外,GORM 允許您用標籤控制欄位級別的許可權。這樣就可以讓一個欄位的許可權是唯讀、只寫、只建立、只更新或者被忽略

type User struct {
  Name string `gorm:"<-:create"` // 允許讀和建立
  Name string `gorm:"<-:update"` // 允許讀和更新
  Name string `gorm:"<-"`        // 允許讀和寫(建立和更新)
  Name string `gorm:"<-:false"`  // 允許讀,禁止寫
  Name string `gorm:"->"`        // 唯讀(除非有自定義設定,否則禁止寫)
  Name string `gorm:"->;<-:create"` // 允許讀和寫
  Name string `gorm:"->:false;<-:create"` // 僅建立(禁止從 db 讀)
  Name string `gorm:"-"`  // 通過 struct 讀寫會忽略該欄位
  Name string `gorm:"-:all"`        // 通過 struct 讀寫、遷移會忽略該欄位
  Name string `gorm:"-:migration"`  // 通過 struct 遷移會忽略該欄位
}

時間慣例

GORM按慣例使用CreatedAt、UpdatedAt來跟蹤建立/更新時間,如果定義了欄位,GORM將在建立/更新時設定當前時間。要使用具有不同名稱的欄位,可以使用標籤autoCreateTime、autoUpdateTime來設定這些欄位。如果希望節省儲存不採用時間格式改為採用UNIX(毫/納)秒,可以簡單地更改欄位的資料型別。

type User struct {
  CreatedAt time.Time // 如果建立時為零,則設定為當前時間
  UpdatedAt int       // 在更新時設定為當前unix秒數,或者在建立時設定為零
  Updated   int64 `gorm:"autoUpdateTime:nano"` // 使用unix納秒作為更新時間
  Updated   int64 `gorm:"autoUpdateTime:milli"`// 使用unix毫秒作為更新時間
  Created   int64 `gorm:"autoCreateTime"`      // 使用unix seconds作為建立時間
}

嵌入結構

例如將Go內建gorm.Model結構體嵌入到User結構體裡

type User struct {
  gorm.Model
  Name string
}
// 這個定義等價於上面
type User struct {
  ID        uint           `gorm:"primaryKey"`
  CreatedAt time.Time
  UpdatedAt time.Time
  DeletedAt gorm.DeletedAt `gorm:"index"`
  Name string
}

但對於普通的struct欄位,可以將其嵌入標籤,例如:

type Author struct {
  Name  string
  Email string
}

type Blog struct {
  ID      int
  Author  Author `gorm:"embedded"`
  Upvotes int32 
}
// 這個定義等價於上面Blog內嵌Author
type Blog struct {
  ID    int64
  Name  string
  Email string
  Upvotes  int32
}

可以使用標籤embeddedPrefix為嵌入欄位的資料庫名稱新增字首,例如:

type Blog struct {
  ID      int
  Author  Author `gorm:"embedded;embeddedPrefix:author_"`
  Upvotes int32
}
// 這個定義等價於上面Blog內嵌Author
type Blog struct {
  ID          int64
  AuthorName  string
  AuthorEmail string
  Upvotes     int32
}

欄位標籤

Tag Name Description
column 資料庫表列名
type 列資料型別,例如:bool, int, uint, float, string, time, bytes,這適用於所有資料庫,並且可以與其他標籤一起使用,如' not null ', ' size ', ' autoIncrement '…指定的資料庫資料型別,如' varbinary(8) '也支援,當使用指定的資料庫資料型別時,它需要是一個完整的資料庫資料型別,例如: MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT
serializer 指定如何將資料序列化和反序列化到db的序列化器, e.g: serializer:json/gob/unixtime
size 指定列資料大小/長度, e.g: size:256
primaryKey 指定列為主鍵
unique 將列指定為唯一約束
default 指定列預設值
precision 指定列精度
scale 指定列的比例
not null 指定列為NOT NULL
autoIncrement 指定列可自動遞增
autoIncrementIncrement 自動遞增步長,控制連續列值之間的間隔
embedded 嵌入欄位
embeddedPrefix 嵌入欄位的列名字首
autoCreateTime 建立時跟蹤當前時間,對於' int '欄位,它將跟蹤Unix秒,使用值' nano ' / ' milli '來跟蹤Unix納米/毫秒, e.g: autoCreateTime:nano
autoUpdateTime 在建立/更新時跟蹤當前時間,對於' int '欄位,它將跟蹤Unix秒,使用值' nano ' / ' milli '來跟蹤Unix納米/毫秒, e.g: autoUpdateTime:milli
index 對多個欄位使用相同的名稱建立複合索引
uniqueIndex 與' index '相同,但建立唯一的索引
check 建立檢查約束, eg: check:age > 13
<- 設定欄位的寫許可權,' <-:create ' create-only欄位,' <-:update ' update-only欄位,' <-:false '無寫許可權,' <- '建立和更新許可權
-> 設定欄位的讀許可權,' ->:false '沒有讀許可權
- 忽略該欄位,' - '無讀寫許可權,' -:migration '無遷移許可權,' -:all '無讀寫遷移許可權
comment 在遷移時為欄位新增註釋

使用

安裝

# 引入gorm
go get -u gorm.io/gorm
# 引入mySQL驅動
go get -u gorm.io/driver/mysql
# 引入sqlite驅動
go get -u gorm.io/driver/sqlite

資料庫連結

import (
  "gorm.io/driver/mysql"
  "gorm.io/gorm"
)

func main() {
  dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
  db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
}

MySQL Driver提供了一些可以在初始化時使用的高階設定,例如:

db, err := gorm.Open(mysql.New(mysql.Config{
  DSN: "gorm:gorm@tcp(127.0.0.1:3306)/gorm?charset=utf8&parseTime=True&loc=Local", // data source name
  DefaultStringSize: 256, // default size for string fields
  DisableDatetimePrecision: true, // disable datetime precision, which not supported before MySQL 5.6
  DontSupportRenameIndex: true, // drop & create when rename index, rename index not supported before MySQL 5.7, MariaDB
  DontSupportRenameColumn: true, // `change` when rename column, rename column not supported before MySQL 8, MariaDB
  SkipInitializeWithVersion: false, // auto configure based on currently MySQL version
}), &gorm.Config{})

連線池

GORM使用資料庫/sql維護連線池

sqlDB, err := db.DB()// SetMaxIdleConns設定空閒連線池中的最大連線數。sqlDB.SetMaxIdleConns(10)// SetMaxOpenConns設定資料庫的最大開啟連線數。sqlDB.SetMaxOpenConns(100)// SetConnMaxLifetime設定連線可能被重用的最大時間。sqlDB.SetConnMaxLifetime(time.Hour)

CRUD 介面

建立

package main

import (
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

type User struct {
	gorm.Model
	Name string
	Age  int8
}

func main() {
	dsn := "root:123456@tcp(mysqlserver8:3306)/test?charset=utf8&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		fmt.Println("data error")
	}

	db.AutoMigrate(&User{})

	// 單個插入建立
	db.Create(&User{Name: "zhangsan", Age: 20})

	users := []User{
		{Name: "lisi", Age: 25},
		{Name: "wangwu", Age: 26},
	}

	// 多個插入並演示返回值
	result := db.Create(users)
	if result != nil {
		fmt.Println(result.RowsAffected)
		fmt.Println(result.Error)
	}

	var user User
    // 根據主鍵ID查詢主鍵值為1的記錄並返回資料
	db.First(&user, 1)
	fmt.Println(user)
}

MySQL的test資料庫及對應表users資訊如下:

// 指定批次處理大小
var users = []User{{Name: "jinzhu_1"}, ...., {Name: "jinzhu_10000"}}
db.CreateInBatches(users, 100)

// 初始化GORM時使用CreateBatchSize選項,所有INSERT在建立記錄和關聯時都將遵循此選項
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
  CreateBatchSize: 1000,
})
db := db.Session(&gorm.Session{CreateBatchSize: 1000})
users = [5000]User{{Name: "jinzhu", Pets: []Pet{pet1, pet2, pet3}}...}
db.Create(&users)

勾點函數範例

package main

import (
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

type User struct {
	gorm.Model
	Name string
	Age  int8
}

func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
	if u.Name == "admin" {
		fmt.Println("before create hint admin")
	}
	return
}

func (u *User) AfterCreate(tx *gorm.DB) (err error) {
	if u.Name == "admin" {
		fmt.Println("after create hint admin")
	}
	return
}

func (u *User) BeforeSave(tx *gorm.DB) (err error) {
	if u.Name == "admin" {
		fmt.Println("before save hint admin")
	}
	return
}

func (u *User) AfterSave(tx *gorm.DB) (err error) {
	if u.Name == "admin" {
		fmt.Println("after save hint admin")
	}
	return
}

func main() {
	dsn := "root:123456@tcp(mysqlserver8:3306)/test?charset=utf8&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		fmt.Println("data error")
	}

	db.Create(&User{Name: "admin", Age: 40})
}

如果你想跳過Hooks方法,可以使用SkipHooks對談模式

DB.Session(&gorm.Session{SkipHooks: true}).Create(&user)
DB.Session(&gorm.Session{SkipHooks: true}).Create(&users)
DB.Session(&gorm.Session{SkipHooks: true}).CreateInBatches(users, 100)

查詢

GORM提供了First、Take、Last方法來從資料庫中檢索單個物件,它在查詢資料庫時新增了LIMIT 1條件,如果沒有找到記錄,它將返回錯誤ErrRecordNotFound。

First和Last方法將按主鍵順序分別查詢第一個和最後一個記錄。只有當指向目標結構的指標作為引數傳遞給方法時,或者使用db.Model()指定模型時,它們才有效。此外,如果沒有為相關模型定義主鍵,則模型將按第一個欄位排序。

package main

import (
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

type User struct {
	gorm.Model
	Name string
	Age  int8
}

func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
	if u.Name == "admin" {
		fmt.Println("before create hint admin")
	}
	return
}

func (u *User) AfterCreate(tx *gorm.DB) (err error) {
	if u.Name == "admin" {
		fmt.Println("after create hint admin")
	}
	return
}

func (u *User) BeforeSave(tx *gorm.DB) (err error) {
	if u.Name == "admin" {
		fmt.Println("before save hint admin")
	}
	return
}

func (u *User) AfterSave(tx *gorm.DB) (err error) {
	if u.Name == "admin" {
		fmt.Println("after save hint admin")
	}
	return
}

func main() {
	dsn := "root:123456@tcp(192.168.50.95:3306)/test?charset=utf8&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		fmt.Println("data error")
	}

	var user, user1, user2, user3 User
	// 獲取一條記錄,下面相當於SELECT * FROM users ORDER BY id LIMIT 1;
	db.First(&user)
	fmt.Println("user=", user)

	// 獲取一條記錄, 下面相當於SELECT * FROM users LIMIT 1;
	db.Take(&user1)
	fmt.Println("user1=", user1)

	// 獲取一條記錄, 下面相當於SELECT * FROM users ORDER BY id DESC LIMIT 1;
	db.Last(&user2)
	fmt.Println(user2)

	// 指定表獲取資料放入map
	result := map[string]interface{}{}
	db.Table("users").Take(&result)
	fmt.Println("result=", result)

	db.First(&user3, "id = ?", 3)
	fmt.Println("user3=", user3)

	var users, users1 []User
	db.Find(&users, []int{1, 2, 3})
	fmt.Println("users=", users)

	// 查詢指定欄位和條件
	db.Select("name").Where("id > ?", 2).Find(&users1)
	fmt.Println("users1=", users1)
}

其他詳細檢視官網

// limit ,SELECT * FROM users LIMIT 3;
db.Limit(3).Find(&users)
// OFFSET ,SELECT * FROM users OFFSET 3;
db.Offset(3).Find(&users)
// group by ,SELECT name, sum(age) as total FROM `users` GROUP BY `name` HAVING name = "group"
db.Model(&User{}).Select("name, sum(age) as total").Group("name").Having("name = ?", "group").Find(&result)
// distinct
db.Distinct("name", "age").Order("name, age desc").Find(&results)
// join ,SELECT users.name, emails.email FROM `users` left join emails on emails.user_id = users.id
db.Model(&User{}).Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Scan(&result{})
// scan,將結果掃描到結構體中的工作方式類似於我們使用Find的方式
var result Result
db.Table("users").Select("name", "age").Where("name = ?", "Antonio").Scan(&result)
// 原始SQL
db.Raw("SELECT name, age FROM users WHERE name = ?", "Antonio").Scan(&result)

高階查詢

  • 智慧選擇欄位:GORM允許通過Select選擇特定的欄位,如果你經常在你的應用程式中使用這個,也許你想為API定義一個更小的結構體,它可以自動選擇特定的欄位。
type User struct {
  ID     uint
  Name   string
  Age    int
  Gender string
  // hundreds of fields
}

type APIUser struct {
  ID   uint
  Name string
}

// 查詢時自動選擇「id」、「name」,SELECT `id`, `name` FROM `users` LIMIT 10
db.Model(&User{}).Limit(10).Find(&APIUser{})
  • 鎖:GORM支援不同型別的鎖。
// SELECT * FROM `users` FOR UPDATE
db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users)
// SELECT * FROM `users` FOR SHARE OF `users`
db.Clauses(clause.Locking{
  Strength: "SHARE",
  Table: clause.Table{Name: clause.CurrentTable},
}).Find(&users)
// SELECT * FROM `users` FOR UPDATE NOWAIT
db.Clauses(clause.Locking{
  Strength: "UPDATE",
  Options: "NOWAIT",
}).Find(&users)
  • SubQuery:子查詢可以巢狀在查詢中,使用* GORM . db物件作為引數時,GORM可以生成子查詢。
// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");
db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&orders)

// SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%")
subQuery := db.Select("AVG(age)").Where("name LIKE ?", "name%").Table("users")
db.Select("AVG(age) as avgage").Group("name").Having("AVG(age) > (?)", subQuery).Find(&results)
  • COUNT:獲取匹配記錄計數。
// SELECT count(1) FROM users WHERE name = 'jinzhu'; (count)
db.Model(&User{}).Where("name = ?", "jinzhu").Count(&count)

修改

  • 儲存所有欄位
db.First(&user)
user.Name = "jinzhu 2"
user.Age = 100
db.Save(&user)
  • 更新欄位
db.First(&user, "age = ?", 25)	
db.Model(&user).Updates(User{Name: "lisinew1", Age: 44})
db.Model(&user).Updates(map[string]interface{}{"Name": "lisinew2", "Age": 35})
db.Model(&user).Update("age", 30)

刪除

如果您的模型包含一個gorm。DeletedAt欄位(包含在gorm.Model中),它將自動獲得軟刪除能力!

// GORM允許使用帶有內聯條件的主鍵刪除物件
db.Delete(&user, 1)

// 假如Email's ID is `10`,DELETE from emails where id = 10;
db.Delete(&email)

// 帶附加條件刪除
db.Where("name = ?", "jinzhu").Delete(&email)

// 永久刪除
db.Unscoped().Delete(&order)

原始SQL

  • 使用Scan查詢Raw SQL
type Result struct {
  ID   int
  Name string
  Age  int
}

var result Result
db.Raw("SELECT id, name, age FROM users WHERE id = ?", 3).Scan(&result)

var age int
db.Raw("SELECT SUM(age) FROM users WHERE role = ?", "admin").Scan(&age)

var users []User
db.Raw("UPDATE users SET name = ? WHERE age = ? RETURNING id, name", "jinzhu", 20).Scan(&users)

  • 執行原始sql
// 執行刪除表資料
db.Exec("DROP TABLE users")
// 執行帶有表示式的更新
db.Exec("UPDATE users SET money = ? WHERE name = ?", gorm.Expr("money * ? + ?", 10000, 1), "jinzhu")
  • DryRun模式
// Session Configuration
type Session struct {
  DryRun                   bool
  PrepareStmt              bool
  NewDB                    bool
  Initialized              bool
  SkipHooks                bool
  SkipDefaultTransaction   bool
  DisableNestedTransaction bool
  AllowGlobalUpdate        bool
  FullSaveAssociations     bool
  QueryFields              bool
  Context                  context.Context
  Logger                   logger.Interface
  NowFunc                  func() time.Time
  CreateBatchSize          int
}


// session mode
stmt := db.Session(&Session{DryRun: true}).First(&user, 1).Statement	
println(stmt.SQL.String()) 
println(stmt.Vars)

  • Row & Rows
// row
row := db.Table("users").Where("name = ?", "jinzhu").Select("name", "age").Row()
row.Scan(&name, &age)

row := db.Raw("select name, age, email from users where name = ?", "jinzhu").Row()
row.Scan(&name, &age, &email)

// rows
rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Select("name, age, email").Rows()
defer rows.Close()
for rows.Next() {
  rows.Scan(&name, &age, &email)

  // do something
}

rows, err := db.Raw("select name, age, email from users where name = ?", "jinzhu").Rows()
defer rows.Close()
for rows.Next() {
  rows.Scan(&name, &age, &email)

  // do something
}

事務

GORM在事務內部執行寫(建立/更新/刪除)操作以確保資料一致性,如果不需要,可以在初始化時禁用它,之後將獲得大約30%以上的效能提升。流程如下:

func CreateAnimals(db *gorm.DB) error {
  // 注意,在事務中使用tx作為資料庫控制程式碼
  tx := db.Begin()
  defer func() {
    if r := recover(); r != nil {
      tx.Rollback()
    }
  }()

  if err := tx.Error; err != nil {
    return err
  }

  if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
     tx.Rollback()
     return err
  }

  if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {
     tx.Rollback()
     return err
  }

  return tx.Commit().Error
}

轉換

  • ID為主鍵:預設情況下,GORM使用名稱為ID的欄位作為表的主鍵。可以通過實現Tabler介面來更改預設表名,例如:
// 可以使用標記primaryKey將其他欄位設定為主鍵
type Animal struct {
  ID     int64
  UUID   string `gorm:"primaryKey"`
  Name   string
  Age    int64
}
  • 複數表名:GORM將結構名複數化為snake_cases作為表名,對於結構User,它的表名按照約定是users
// 轉換表名為my_users
func (User) TableName() string {
	return "my_users"
}
  • GORM允許使用者通過覆蓋預設的NamingStrategy來更改預設的命名約定,該策略用於構建TableName, ColumnName, JoinTableName, RelationshipFKName, CheckerName, IndexName
type Animal struct {
  AnimalID int64     `gorm:"column:beast_id"`         // 將列名設定為 `beast_id`
  Birthday time.Time `gorm:"column:day_of_the_beast"` // 將列名設定為 `day_of_the_beast`
  Age      int64     `gorm:"column:age_of_the_beast"` // 將列名設定為 `age_of_the_beast`
}

分片

Sharding 是一個高效能的 Gorm 分表中介軟體。它基於 Conn 層做 SQL 攔截、AST 解析、分表路由、自增主鍵填充,帶來的額外開銷極小。對開發者友好、透明,使用上與普通 SQL、Gorm 查詢無差別,只需要額外注意一下分表鍵條件。 為您提供高效能的資料庫存取。

功能特點

  • 非侵入式設計, 載入外掛,指定設定,既可實現分表。
  • 輕快, 非基於網路層的中介軟體,像 Go 一樣快
  • 支援多種資料庫。 PostgreSQL 已通過測試,MySQL 和 SQLite 也在路上。
  • 多種主鍵生成方式支援(Snowflake, PostgreSQL Sequence, 以及自定義支援)Snowflake 支援從主鍵中確定分表鍵。

設定分片中介軟體,註冊想要分片的表

import (
  "fmt"

  "gorm.io/driver/postgres"
  "gorm.io/gorm"
  "gorm.io/sharding"
)

dsn := "postgres://localhost:5432/sharding-db?sslmode=disable"
db, err := gorm.Open(postgres.New(postgres.Config{DSN: dsn}))

db.Use(sharding.Register(sharding.Config{
    ShardingKey:         "user_id",
    NumberOfShards:      64,
    PrimaryKeyGenerator: sharding.PKSnowflake,
}, "orders").Register(sharding.Config{
    ShardingKey:         "user_id",
    NumberOfShards:      256,
    PrimaryKeyGenerator: sharding.PKSnowflake,
    // 對於show up give notifications, audit_logs表使用相同的分片規則。
}, Notification{}, AuditLog{}))

序列化

序列化器是一個可延伸的介面,允許自定義如何使用databasae序列化和反序列化資料。

package main

import (
	"database/sql/driver"
	"encoding/json"
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

type Profile struct {
	Email  string `json:"email"`
	Mobile string `json:"mobile"`
}

type User struct {
	gorm.Model
	Name    string  `json:"name"`
	Age     int8    `json:"age"`
	Profile Profile `json:"profile" gorm:"type:json;comment:'個人資訊'"`
}

// 轉換表名
func (User) TableName() string {
	return "new_users"
}

// Value 儲存資料的時候轉換為字串
func (t Profile) Value() (driver.Value, error) {
	return json.Marshal(t)
}

// Scan 讀取資料的時候轉換為json
func (t *Profile) Scan(value interface{}) error {
	return json.Unmarshal(value.([]byte), &t)
}

func main() {
	dsn := "root:123456@tcp(mysqlserver8:3306)/test?charset=utf8&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		fmt.Println("data error")
	}
	db.AutoMigrate(&User{})

	user := User{
		Name: "劉海",
		Age:  23,
		Profile: Profile{
			Email:  "[email protected]",
			Mobile: "18822334455",
		},
	}
	db.Create(&user)

	var user1 User
	db.Debug().Where("profile->'$.mobile'=(?)", "18822334455").First(&user1)
	fmt.Println(user1)
}

檢視mysql已有新建立的new_users資料庫和對應的資料

  • 本人部落格網站IT小神 www.itxiaoshen.com