GORM學習入門

ice_moss發表於2022-06-23

文章介紹

在篇內容介紹基於golang的gorm,這裡我將簡單介紹如何安裝,連線資料庫(以MySQL為例),以及基本的curd操作

安裝

gorm官方文件

go get -u gorm.io/gorm
go get -u gorm.io/driver/sqlite

模型定義

模型是標準的 struct,由 Go 的基本資料型別、實現了 ScannerValuer 介面的自定義型別及其指標或別名組成,我們最後的表名就是結構體的和結構體名一致,當然欄位也一致的

例如:

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 允許您自定義配置它們

自定義配置

使用 ID 作為主鍵

預設情況下,GORM 會使用 ID 作為表的主鍵。

type User struct {
  ID   string // 預設情況下,名為 `ID` 的欄位會作為表的主鍵
  Name string
}

你可以透過標籤 primaryKey 將其它欄位設為主鍵

// 將 `UUID` 設為主鍵
type Animal struct {
  ID     int64
  UUID   string `gorm:"primaryKey"`
  Name   string
  Age    int64
}

此外,您還可以看看 複合主鍵

gorm.Model

GORM 定義一個 gorm.Model 結構體,其包括欄位 IDCreatedAtUpdatedAtDeletedAt

// gorm.Model 的定義
type Model struct {
  ID        uint           `gorm:"primaryKey"`
  CreatedAt time.Time
  UpdatedAt time.Time
  DeletedAt gorm.DeletedAt `gorm:"index"`
}

您可以將它嵌入到您的結構體中,以包含這幾個欄位

連線資料庫

這裡我們以mysql為例:

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

func main() {
  // 參考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 獲取詳情
  dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
  //注意:pass為MySQL資料庫的管理員密碼,dbname為要連線的資料庫
  db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
}

MySQl 驅動程式提供了 一些高階配置 可以在初始化過程中使用

建表

這裡我們先連線資料庫,為了方便檢視SQL語句,我們將記入日誌

package main

import (
    "log"
    "os"
    "time"

    "gorm.io/driver/mysql"
    "gorm.io/gorm"
    "gorm.io/gorm/logger"
)

//定義表結構
type Producttest struct {
    gorm.Model
    Name  string
    Code  string
    Price uint
}

func main() {
    // 參考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 獲取詳情
    dsn := "root:Qq/2013XiaoKUang@tcp(127.0.0.1:3306)/gorm_test?charset=utf8mb4&parseTime=True&loc=Local"

    //用於輸出使用的sql語句
    newLogger := logger.New(
        log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日誌輸出的目標,字首和日誌包含的內容——譯者注)
        logger.Config{
            SlowThreshold:             time.Second, // 慢 SQL 閾值
            LogLevel:                  logger.Info, // 日誌級別
            IgnoreRecordNotFoundError: true,        // 忽略ErrRecordNotFound(記錄未找到)錯誤
            Colorful:                  true,        // 禁用彩色列印
        },
    )

    //開啟mysql服務中對應的資料庫
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
        Logger: newLogger,
    })
    if err != nil {
        panic(err)
    }

    //AutoMigrate 為給定模式執行自動遷移,建立Product型別的資料表
    err = db.AutoMigrate(&Producttest{})
    if err != nil {
        log.Fatal("建表失敗", err)
    }

我們看到建表成功:

mysql> show tables;
+———————+
| Tables_in_gorm_test |
+———————+
| dities |
| products |
| producttests |
| subways |
| test |
| test1 |
| user2 |
| user_infos |
| users |
+———————+
9 rows in set (0.00 sec)

快速入門

新增資料

這裡以結構體的方式插入資料

    // Create 新增資料
    db.Create(&Producttest{Code: "01", Name: "golang程式設計", Price: 100})
    db.Create(&Producttest{Code: "02", Name: "python入門", Price: 200})

然後我們可以看到輸出結果:


2022/06/23 09:31:03 /Users/feng/go/src/GormStart/ch01/main.go:51
[16.193ms] [rows:1] INSERT INTO `producttests` (`created_at`,`updated_at`,`deleted_at`,`name`,`code`,`price`) VALUES ('2022-06-23 09:31:03.35','2022-06-23 09:31:03.35',NULL,'golang程式設計','01',100)

2022/06/23 09:31:03 /Users/feng/go/src/GormStart/ch01/main.go:52
[2.331ms] [rows:1] INSERT INTO `producttests` (`created_at`,`updated_at`,`deleted_at`,`name`,`code`,`price`) VALUES ('2022-06-23 09:31:03.364','2022-06-23 09:31:03.364',NULL,'python入門','02',200)
Process 96312 has exited with status 0
Detaching
dlv dap (96294) exited with code: 0

這樣我們就將兩條資料插入了資料庫中

查詢資料
var product Producttest  //需要例項化一個表結構
    db.First(&product, 2) // 根據整型主鍵查詢
    fmt.Println(product.Name)
    db.First(&product, "code = ?", "02") // 查詢 code 欄位值為 D42 的記錄
    fmt.Println(product)

查詢結果:

[3.323ms] [rows:1] SELECT * FROM `producttests` WHERE `producttests`.`id` = 2 AND `producttests`.`deleted_at` IS NULL ORDER BY `producttests`.`id` LIMIT 1
python入門

[1.002ms] [rows:1] SELECT * FROM `producttests` WHERE code = '02' AND `producttests`.`deleted_at` IS NULL AND `producttests`.`id` = 2 ORDER BY `producttests`.`id` LIMIT 1
{{2 2022-06-23 09:31:03.364 +0800 CST 2022-06-23 09:31:03.364 +0800 CST {0001-01-01 00:00:00 +0000 UTC false}} python入門 02 200}
更新資料
db.Model(&Producttest{}).Where("Code = ?", "01").Update("Price", "50")
// UPDATE users SET Price='50', updated_at='2022-06-23 10:00:23.654' WHERE Code='01';

輸出結果:

[9.204ms] [rows:0] UPDATE `producttests` SET `price`='50',`updated_at`='2022-06-23 10:05:49.719' WHERE Code = '01' AND `producttests`.`deleted_at` IS NULL
刪除資料
var product Producttest
db.Delete(&product, 1) //刪除主碼為1的資料

新增資料(C)

我們先定義表結構

type User struct {
    Name     string
    Age      uint
    Birthday time.Time
  Addr     string
  Work     string
}

連線資料庫:

func main() {
    // 參考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 獲取詳情
    dsn := "root:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"

    //用於輸出使用的sql語句
    newLogger := logger.New(
        log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日誌輸出的目標,字首和日誌包含的內容——譯者注)
        logger.Config{
            SlowThreshold:             time.Second, // 慢 SQL 閾值
            LogLevel:                  logger.Info, // 日誌級別
            IgnoreRecordNotFoundError: true,        // 忽略ErrRecordNotFound(記錄未找到)錯誤
            Colorful:                  true,        // 禁用彩色列印
        },
    )

    //開啟mysql服務中對應的資料庫
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
        Logger: newLogger,
    })
    if err != nil {
        panic(err)
    }

      //建立表
    _ = db.AutoMigrate(&User{})

建表成功:

[18.454ms] [rows:0] CREATE TABLE `users` (`name` longtext,`age` bigint unsigned,`birthday` datetime(3) NULL,`addr` longtext,`work` longtext)

新增資料項:

//新增資料項
    var Age uint = 18
    for i := 0; i < 5; i++ {
        time.Sleep(time.Second * 2)
        _ = db.Create(&User{Name: fmt.Sprintf("小楊%d", i), Age: Age, Birthday: time.Now()})
        Age++
    }

我們可以看到:


2022/06/23 10:35:28 /Users/feng/go/src/GormStart/ch03/main.go:73
[6.255ms] [rows:1] INSERT INTO `users` (`name`,`age`,`birthday`,`addr`,`work`) VALUES ('小楊0',18,'2022-06-23 10:35:28.267','','')

2022/06/23 10:35:30 /Users/feng/go/src/GormStart/ch03/main.go:73
[4.036ms] [rows:1] INSERT INTO `users` (`name`,`age`,`birthday`,`addr`,`work`) VALUES ('小楊1',19,'2022-06-23 10:35:30.275','','')

2022/06/23 10:35:32 /Users/feng/go/src/GormStart/ch03/main.go:73
[5.979ms] [rows:1] INSERT INTO `users` (`name`,`age`,`birthday`,`addr`,`work`) VALUES ('小楊2',20,'2022-06-23 10:35:32.284','','')

2022/06/23 10:35:34 /Users/feng/go/src/GormStart/ch03/main.go:73
[4.307ms] [rows:1] INSERT INTO `users` (`name`,`age`,`birthday`,`addr`,`work`) VALUES ('小楊3',21,'2022-06-23 10:35:34.292','','')

2022/06/23 10:35:36 /Users/feng/go/src/GormStart/ch03/main.go:73
[3.353ms] [rows:1] INSERT INTO `users` (`name`,`age`,`birthday`,`addr`,`work`) VALUES ('小楊4',22,'2022-06-23 10:35:36.296','','')
Process 97602 has exited with status 0
單值插入:

當然我們可以使用:db.create()

db.Create(&User{Name: "小李", Age: 20, Birthday: time.Now(), Addr: "北京", Work: "程式設計師"})

即:

[2.769ms] [rows:1] INSERT INTO `users` (`name`,`age`,`birthday`,`addr`,`work`) VALUES ('小李',20,'2022-06-23 10:42:39.84','北京','程式設計師')
批次插入

現在我們修改一下表結構

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

將原來的users表刪除

然後建立表:

//建立表
    _ = db.AutoMigrate(&User{})
//批次插入資料
    var users = []User{{Name: "小楊"}, {Name: "小張"}, {Name: "小李"}, {Name: "小馮"}}
    db.Create(&users)
    for _, u := range users {
        fmt.Println(u.Name)
    }

輸出結果:

[2.378ms] [rows:4] INSERT INTO `users` (`name`,`email`,`age`,`addr`,`work`,`birthday`,`member_number`,`activated_at`,`created_at`,`updated_at`) VALUES ('小楊',NULL,0,'','',NULL,NULL,NULL,'2022-06-23 10:57:56.088','2022-06-23 10:57:56.088'),('小張',NULL,0,'','',NULL,NULL,NULL,'2022-06-23 10:57:56.088','2022-06-23 10:57:56.088'),('小李',NULL,0,'','',NULL,NULL,NULL,'2022-06-23 10:57:56.088','2022-06-23 10:57:56.088'),('小馮',NULL,0,'','',NULL,NULL,NULL,'2022-06-23 10:57:56.088','2022-06-23 10:57:56.088')
小楊
小張
小李
小馮

我們還可以這樣做:

db.Model(&User{}).Create(map[string]interface{}{
        "Name": "小剛", "Age": 25, "Addr": "廣州",
    })

查詢資料(R)

按排序找找
  1. 升序
//檢索單個資料,升序
    var user User
    _ = db.First(&user)
    fmt.Println(user)

輸出:

[1.218ms] [rows:1] SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1
{1 小楊 <nil> 0   <nil> { false} {0001-01-01 00:00:00 +0000 UTC false} 2022-06-23 10:57:56.088 +0800 CST 2022-06-23 10:57:56.088 +0800 CST}
  1. 降序
//降序
    var user User
    _ = db.Last(&user)
    fmt.Println(user)
按照位置查詢
//按資料表中的位置
    var user User
    _ = db.Take(&user, 2)
    fmt.Println(user)

輸出:

[1.653ms] [rows:1] SELECT * FROM `users` WHERE `users`.`id` = 2 LIMIT 1
{2 小張 <nil> 0   <nil> { false} {0001-01-01 00:00:00 +0000 UTC false} 2022-06-23 10:57:56.088 +0800 CST 2022-06-23 10:57:56.088 +0800 CST}
按照主鍵查詢
    //透過主鍵查詢
    var user User
    result := db.First(&user, 3)
    if errors.Is(result.Error, gorm.ErrRecordNotFound) {
        fmt.Println("資料未找到")
    }
    fmt.Println(user)
查詢表中所有資料
//檢索全部物件
    var users []User
    result := db.Find(&users)
    for _, user := range users {
        fmt.Println(user)
    }
    fmt.Println(result.RowsAffected)

輸出:

[1.553ms] [rows:5] SELECT * FROM `users`
{1 小楊 <nil> 0   <nil> { false} {0001-01-01 00:00:00 +0000 UTC false} 2022-06-23 10:57:56.088 +0800 CST 2022-06-23 10:57:56.088 +0800 CST}
{2 小張 <nil> 0   <nil> { false} {0001-01-01 00:00:00 +0000 UTC false} 2022-06-23 10:57:56.088 +0800 CST 2022-06-23 10:57:56.088 +0800 CST}
{3 小李 <nil> 0   <nil> { false} {0001-01-01 00:00:00 +0000 UTC false} 2022-06-23 10:57:56.088 +0800 CST 2022-06-23 10:57:56.088 +0800 CST}
{4 小馮 <nil> 0   <nil> { false} {0001-01-01 00:00:00 +0000 UTC false} 2022-06-23 10:57:56.088 +0800 CST 2022-06-23 10:57:56.088 +0800 CST}
{5 小剛 <nil> 25 廣州  <nil> { false} {0001-01-01 00:00:00 +0000 UTC false} 0001-01-01 00:00:00 +0000 UTC 0001-01-01 00:00:00 +0000 UTC}
5
根據條件查詢
  1. 匹配一條資料:First()

這裡db.Where().First()只會匹配一條資料

//根據條件檢索
    var users []User
    //匹配一條資料
    db.Where("name= ?", "小楊").First(&users)
    for _, user := range users {
        fmt.Println(user)
    }

輸出:

[2.400ms] [rows:1] SELECT * FROM `users` WHERE name= '小楊' ORDER BY `users`.`id` LIMIT 1
{1 小楊 <nil> 0   <nil> { false} {0001-01-01 00:00:00 +0000 UTC false} 2022-06-23 10:57:56.088 +0800 CST 2022-06-23 10:57:56.088 +0800 CST}
  1. 匹配多條資料:Find()
    db.Where("Addr = ?", "北京").Find((&users))
    for _, user := range users {
        fmt.Println(user)
    }

輸出:

[2.133ms] [rows:2] SELECT * FROM `users` WHERE Addr = '北京'
{6 小熊 <nil> 0 北京  <nil> { false} {0001-01-01 00:00:00 +0000 UTC false} 2022-06-23 11:30:17.018 +0800 CST 2022-06-23 11:30:17.018 +0800 CST}
{8 小張 <nil> 0 北京  <nil> { false} {0001-01-01 00:00:00 +0000 UTC false} 2022-06-23 11:33:21.414 +0800 CST 2022-06-23 11:33:21.414 +0800 CST}

注意:當我們不知道sql表中欄位名的時候可以直接使用結構體,這樣可以直接遮蔽資料表底層邏輯,這樣我們就可以不用關心資料表的結構了

例如:

//此方法可遮蔽底層SQL資料表欄位
    var user User
    db.Where(&User{Name: "小楊"}).First(&user)
    fmt.Println(user)
  1. 根據條件IN檢索
//根據條件檢索 IN 
//查詢age等於18、19、20的資料
    var users []User
    db.Where("age IN ?", []uint{18, 19, 20}).Find(&users)
    for _, user := range users {
        fmt.Println(user)
    }

輸出:

[0.917ms] [rows:3] SELECT * FROM `users` WHERE age IN (18,19,20)
{9 小周 <nil> 18 上海  <nil> { false} {0001-01-01 00:00:00 +0000 UTC false} 2022-06-23 11:49:05.759 +0800 CST 2022-06-23 11:49:05.759 +0800 CST}
{10 小周 <nil> 19 上海  <nil> { false} {0001-01-01 00:00:00 +0000 UTC false} 2022-06-23 11:49:05.763 +0800 CST 2022-06-23 11:49:05.763 +0800 CST}
{11 小周 <nil> 20 上海  <nil> { false} {0001-01-01 00:00:00 +0000 UTC false} 2022-06-23 11:49:05.77 +0800 CST 2022-06-23 11:49:05.77 +0800 CST}
  1. 根據條件AND檢索
//根據條件檢索 AND
//查詢地址在北京並且大於等於18歲的人
    var users []User
    db.Where("addr=? AND age>=?", "北京", 18).Find(&users)
    for _, user := range users {
        fmt.Println(user)
    }

輸出:

[2.181ms] [rows:3] SELECT * FROM `users` WHERE addr='北京' AND age>=18
{12 小董 <nil> 23 北京  <nil> { false} {0001-01-01 00:00:00 +0000 UTC false} 2022-06-23 11:49:05.773 +0800 CST 2022-06-23 11:49:05.773 +0800 CST}
{13 小周 <nil> 18 北京  <nil> { false} {0001-01-01 00:00:00 +0000 UTC false} 2022-06-23 11:54:55.964 +0800 CST 2022-06-23 11:54:55.964 +0800 CST}
{16 小畫 <nil> 23 北京  <nil> { false} {0001-01-01 00:00:00 +0000 UTC false} 2022-06-23 11:54:55.973 +0800 CST 2022-06-23 11:54:55.973 +0800 CST}

根據條件OR 檢索

var users []User
db.Where("addr=? OR age>=?", "北京", 20).Find(&users)
for _, user := range users {
    fmt.Println(user)
}

輸出:

[1.749ms] [rows:8] SELECT * FROM `users` WHERE addr='北京' OR age>=20
{5 小剛 <nil> 25 廣州  <nil> { false} {0001-01-01 00:00:00 +0000 UTC false} 0001-01-01 00:00:00 +0000 UTC 0001-01-01 00:00:00 +0000 UTC}
{6 小熊 <nil> 0 北京  <nil> { false} {0001-01-01 00:00:00 +0000 UTC false} 2022-06-23 11:30:17.018 +0800 CST 2022-06-23 11:30:17.018 +0800 CST}
{8 小張 <nil> 0 北京  <nil> { false} {0001-01-01 00:00:00 +0000 UTC false} 2022-06-23 11:33:21.414 +0800 CST 2022-06-23 11:33:21.414 +0800 CST}
{11 小周 <nil> 20 上海  <nil> { false} {0001-01-01 00:00:00 +0000 UTC false} 2022-06-23 11:49:05.77 +0800 CST 2022-06-23 11:49:05.77 +0800 CST}
{12 小董 <nil> 23 北京  <nil> { false} {0001-01-01 00:00:00 +0000 UTC false} 2022-06-23 11:49:05.773 +0800 CST 2022-06-23 11:49:05.773 +0800 CST}
{13 小周 <nil> 18 北京  <nil> { false} {0001-01-01 00:00:00 +0000 UTC false} 2022-06-23 11:54:55.964 +0800 CST 2022-06-23 11:54:55.964 +0800 CST}
{15 小楊 <nil> 20 上海  <nil> { false} {0001-01-01 00:00:00 +0000 UTC false} 2022-06-23 11:54:55.971 +0800 CST 2022-06-23 11:54:55.971 +0800 CST}
{16 小畫 <nil> 23 北京  <nil> { false} {0001-01-01 00:00:00 +0000 UTC false} 2022-06-23 11:54:55.973 +0800 CST 2022-06-23 11:54:55.973 +0800 CST}

使用struct & map

//使用struct
    var users []User
    db.Where(&User{Name: "小周", Age: 18, Addr: "北京"}).Find(&users)
    for _, user := range users {
        fmt.Println(user)
    }

輸出:

[2.005ms] [rows:1] SELECT * FROM `users` WHERE `users`.`name` = '小周' AND `users`.`age` = 18 AND `users`.`addr` = '北京'
{13 小周 <nil> 18 北京  <nil> { false} {0001-01-01 00:00:00 +0000 UTC false} 2022-06-23 11:54:55.964 +0800 CST 2022-06-23 11:54:55.964 +0800 CST}
    //使用map
    var users []User
    db.Where(map[string]interface{}{"name": "小周", "age": 18}).Find(&users)
    // SELECT * FROM users WHERE name = "jinzhu" AND age = 20;
    for _, user := range users {
        fmt.Println(user)
    }

輸出:

[4.219ms] [rows:2] SELECT * FROM `users` WHERE `age` = 18 AND `name` = '小周'
{9 小周 <nil> 18 上海  <nil> { false} {0001-01-01 00:00:00 +0000 UTC false} 2022-06-23 11:49:05.759 +0800 CST 2022-06-23 11:49:05.759 +0800 CST}
{13 小周 <nil> 18 北京  <nil> { false} {0001-01-01 00:00:00 +0000 UTC false} 2022-06-23 11:54:55.964 +0800 CST 2022-06-23 11:54:55.964 +0800 CST}

更新資料(U)

儲存所有欄位

Save 會儲存所有的欄位,即使欄位是零值

//透過save方法更新
    var user User
    _ = db.First(&user)
    fmt.Println(user)

    user.Name = "小曠"
    user.Age = 22
    user.ID = 17
    user.Addr = "深圳"
    user.Work = "go開發工程師&gis開發工程師"
    _ = db.Save(&user)
    fmt.Println(user)

輸出結果:

[rows:1] INSERT INTO `users` (`name`,`email`,`age`,`addr`,`work`,`birthday`,`member_number`,`activated_at`,`created_at`,`updated_at`,`id`) VALUES ('小曠',NULL,22,'深圳','go開發工程師&gis開發工程師',NULL,NULL,NULL,'2022-06-23 10:57:56.088','2022-06-23 13:28:10.252',17)
{17 小曠 <nil> 22 深圳 go開發工程師&gis開發工程師 <nil> { false} {0001-01-01 00:00:00 +0000 UTC false} 2022-06-23 10:57:56.088 +0800 CST 2022-06-23 13:28:10.252 +0800 CST}
更新指定欄位
//透過指定欄位更新
//將age等於0的更新為20
    var user User
    db.Model(&user).Where("age", 0).Update("age", 20)

輸出結果:

[rows:7] UPDATE `users` SET `age`=20,`updated_at`='2022-06-23 13:33:50.548' WHERE `age` = 0
db.Model(&user).Where("addr", "北京").Update("work", "go開發工程師")

輸出結果:

[rows:5] UPDATE `users` SET `work`='go開發工程師',`updated_at`='2022-06-23 13:38:32.69' WHERE `addr` = '北京'

刪除資料(D)

刪除一條記錄

刪除一條記錄時,刪除物件需要指定主鍵,否則會觸發批次 Delete

根據主鍵刪除
//根據主鍵刪除
    var user User
    db.Delete(&user, 17)

輸出:

[rows:1] DELETE FROM `users` WHERE `users`.`id` = 17
//使用slice
    var user User
    db.Delete(&user, []int{18, 19, 20, 21})

輸出:

[rows:0] DELETE FROM `users` WHERE `users`.`id` IN (18,19,20,21)
軟刪除

如果您的模型包含了一個 gorm.deletedat 欄位(gorm.Model 已經包含了該欄位),它將自動獲得軟刪除的能力!

擁有軟刪除能力的模型呼叫 Delete 時,記錄不會從資料庫中被真正刪除。但 GORM 會將 DeletedAt 置為當前時間, 並且你不能再透過普通的查詢方法找到該記錄。

    // 批次刪除
    db.Where("age = ?", 20).Delete(&User{})

輸出:

rows:8] DELETE FROM `users` WHERE age = 20
// 在查詢時會忽略被軟刪除的記錄
db.Where("age = 20").Find(&user)
fmt.Println(user)

輸出:查詢無果,已經被軟刪除了

{0  <nil> 0   <nil> { false} {0001-01-01 00:00:00 +0000 UTC false} 0001-01-01 00:00:00 +0000 UTC 0001-01-01 00:00:00 +0000 UTC}

關聯

Belongs To

belongs to 會與另一個模型建立了一對一的連線。 這種模型的每一個例項都“屬於”另一個模型的一個例項。
例如,假如我們的應用包含 user 和 company,並且每個 user 能且只能被分配給一個 company。下面的型別就表示這種關係。 注意,在 UserTest 物件中,有一個和 CompanyTest 一樣的 CompanyTestID。 預設情況下, CompanyTestID 被隱含地用來在 UserTestCompanyTest 之間建立一個外來鍵關係, 因此必須包含在 UserTest 結構體中才能填充 CompanyTest 內部結構體
外來鍵簡單解釋:一張表中的外來鍵,該表關聯的另一張表的主鍵,例如:UserTest的外來鍵CompanyTestID就為CompanyTest表的主鍵
例項:

// `UserTest` 屬於 `CompanyTest`,`CompanyTestID` 是外來鍵
type UserTest struct {
  gorm.Model
  Name          string
  CompanyTestID int // CompanyTestID會預設為外來鍵
  CompanyTest   CompanyTest  //這裡必須外來鍵名字首一致
}

type CompanyTest struct {
  ID   int
  Name string
}

建表:

func main() {
   // 參考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 獲取詳情
  dsn := "root:password@tcp(127.0.0.1:3306)/gorm_test?charset=utf8mb4&parseTime=True&loc=Local"

  //用於輸出使用的sql語句
  newLogger := logger.New(
      log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日誌輸出的目標,字首和日誌包含的內容——譯者注)
  logger.Config{
         SlowThreshold:             time.Second, // 慢 SQL 閾值
  LogLevel:                  logger.Info, // 日誌級別
  IgnoreRecordNotFoundError: true, // 忽略ErrRecordNotFound(記錄未找到)錯誤
  Colorful:                  true, // 禁用彩色列印
  },
  )
   //開啟mysql服務中對應的資料庫
  db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
      Logger: newLogger,
  })
   if err != nil {
      panic(err)
   }

   err = db.AutoMigrate(&UserTest{})
   if err != nil {
      panic(err)
   }

這裡需要注意,gorm會先建立CompanyTest 表然後建立UserTest

寫入資料:

//分別插入資料,並且自動寫入外來鍵值
db.Create(&UserTest{
   Name: "ice_moss",
   CompanyTest: CompanyTest{
      Name: "騰訊",
  },
})

在插入資料的時候,也是先對關聯表CompanyTest插入,然後插入UserTest

或者指定ID

//可指定外來鍵
db.Create(&UserTest{
   Name: "ice_moss5",
  CompanyTest: CompanyTest{
      ID:   3,
  Name: "位元組跳動",
  },
})

如下圖
UserTest:
GORM學習入門

CompanyTest:

GORM學習入門

關聯查詢
db.Preload()
//多表關聯查詢
var User1 []UserTest
db.Preload("CompanyTest").Find(&User1)
for key, value := range User1 {
   fmt.Println(key, value)
}
db.Joins()
//多表關聯查詢
var User2 UserTest
db.Joins("CompanyTest").First(&User2)
fmt.Println(User2.Name, User2.CompanyTest.Name)
has many

has many 與另一個模型建立了一對多的連線。 不同於 has one,擁有者可以有零或多個關聯模型。
例如,您的應用包含 user 和 credit card 模型,且每個 user 可以有多張 credit card。

// User 有多張 CreditCard,UserID 是外來鍵,多個CreditCard可以對應一個User,所以每一個CreditCard都需要有外來鍵指向User
type User struct {
    gorm.Model
    CreditCards []CreditCard
}

type CreditCard struct {
    gorm.Model
    Number string
    UserID uint   //外來鍵
}
重寫外來鍵
type User struct {
   gorm.Model
  CreditCards []CreditCard `gorm:"foreignKey:UserRefer"`
}

type CreditCard struct {
   gorm.Model
  Number    string
  UserRefer uint //外來鍵, 每一張卡需要指向唯一使用者,所以每一個CreditCard需要使用外來鍵指向User
}

現在來插入幾條記錄

//插入資料,兩條卡記錄指向同一user
var user User
db.Create(&user)
db.Create(&CreditCard{
   Number:    "12",
  UserRefer: user.ID,  //CreditCard外來鍵為User的主鍵
})
db.Create(&CreditCard{
   Number:    "34",
  UserRefer: user.ID,  //給外來鍵
})

CreditCard:
GORM學習入門

User:

GORM學習入門

反向查詢:

//使用User做反向查詢
var user User
db.Preload("CreditCards").First(&user)
for _, value := range user.CreditCards {
   fmt.Println(value.Number)
}

輸出:

[2.587ms] [rows:2] SELECT * FROM `credit_cards` WHERE `credit_cards`.`user_refer` = 1 AND `credit_cards`.`deleted_at` IS NULL

[5.851ms] [rows:1] SELECT * FROM `users` WHERE `users`.`deleted_at` IS NULL ORDER BY `users`.`id` LIMIT 1
12
34
Many to Many

Many to Many 會在兩個 model 中新增一張連線表。

例如,您的應用包含了 user 和 language,且一個 user 可以說多種 language,多個 user 也可以說一種 language。

反向引用
// User 擁有並屬於多種 language,`user_languages` 是連線表
type User struct {
   gorm.Model
  Languages []*Language `gorm:"many2many:user_languages;"`
}

type Language struct {
   gorm.Model
  Name string
  Users []*User `gorm:"many2many:user_languages;"`
}

todo

// User 擁有並屬於多種 language,`user_languages` 是連線表
type User struct {
   gorm.Model
   Languages []Language `gorm:"many2many:user_languages;"`
}

type Language struct {
   gorm.Model
   Name string
}

當使用 GORM 的 AutoMigrateUser 建立表時,GORM 會自動建立連線表
建表:
users:

GORM學習入門

languages:

GORM學習入門

user_languages:

GORM學習入門

插入記錄:

//寫入資料
var language []Language
language = append(language, Language{Name: "golang"})
language = append(language, Language{Name: "c++"})
language = append(language, Language{Name: "java"})
db.Create(&User{
   Languages: language,
})

輸出:一個執行三條sql語句,分別對三張表進行插入

2022/07/29 22:58:29 /Users/feng/go/src/GormStart/ch11/mian.go:52
[7.552ms] [rows:3] INSERT INTO `languages` (`created_at`,`updated_at`,`deleted_at`,`name`) VALUES ('2022-07-29 22:58:29.731','2022-07-29 22:58:29.731',NULL,'golang'),('2022-07-29 22:58:29.731','2022-07-29 22:58:29.731',NULL,'c++'),('2022-07-29 22:58:29.731','2022-07-29 22:58:29.731',NULL,'java') ON DUPLICATE KEY UPDATE `id`=`id`

2022/07/29 22:58:29 /Users/feng/go/src/GormStart/ch11/mian.go:52
[1.197ms] [rows:3] INSERT INTO `user_languages` (`user_id`,`language_id`) VALUES (1,1),(1,2),(1,3) ON DUPLICATE KEY UPDATE `user_id`=`user_id`

2022/07/29 22:58:29 /Users/feng/go/src/GormStart/ch11/mian.go:52
[16.804ms] [rows:1] INSERT INTO `users` (`created_at`,`updated_at`,`deleted_at`) VALUES ('2022-07-29 22:58:29.727','2022-07-29 22:58:29.727',NULL)

users:

GORM學習入門

languages:
GORM學習入門

user_languages:

GORM學習入門

查詢:

var user User
db.Preload("Languages").Find(&user)
for _, value := range user.Languages {
   fmt.Println(value.Name)
}

輸出:

[0.523ms] [rows:3] SELECT * FROM `user_languages` WHERE `user_languages`.`user_id` = 1

[0.641ms] [rows:3] SELECT * FROM `languages` WHERE `languages`.`id` IN (1,2,3) AND `languages`.`deleted_at` IS NULL

[3.467ms] [rows:1] SELECT * FROM `users` WHERE `users`.`deleted_at` IS NULL
golang
c++
java

獲取資料的另一種方式:

var user User
db.First(&user)
var languges []Language
_ = db.Model(&user).Association("Languages").Find(&languges)
for _, value := range languges {
   fmt.Println(value.Name)
}

輸出:

[3.020ms] [rows:1] SELECT * FROM `users` WHERE `users`.`deleted_at` IS NULL ORDER BY `users`.`id` LIMIT 1

[1.765ms] [rows:3] SELECT `languages`.`id`,`languages`.`created_at`,`languages`.`updated_at`,`languages`.`deleted_at`,`languages`.`name` FROM `languages` JOIN `user_languages` ON `user_languages`.`language_id` = `languages`.`id` AND `user_languages`.`user_id` = 1 WHERE `languages`.`deleted_at` IS NULL
golang
c++
java

下面來介紹一個多對多的例項:

// User 擁有並屬於多種 language,`user_languages` 是連線表type User struct {
   Name string
  gorm.Model
  Languages []*Language `gorm:"many2many:user_languages;"`
}

type Language struct {
   gorm.Model
  Name  string
  Users []*User `gorm:"many2many:user_languages;"`
}

寫入記錄:

//寫入資料
var language []*Language
language = append(language, &Language{Name: "golang"})
language = append(language, &Language{Name: "c++"})
language = append(language, &Language{Name: "java"})
db.Create(&User{
   Languages: language,
})

var user []*User
user = append(user, &User{Name: "ice_moss1"})
user = append(user, &User{Name: "ice_moss2"})
user = append(user, &User{Name: "ice_moss3"})
db.Create(&Language{
   Users: user,
})

users:

GORM學習入門

languages:

GORM學習入門

user_languages:

GORM學習入門
從關聯表中我們可以很直觀的看出:users中ID為1的有languages中有ID為1、2、3的與之對應,同樣,languages中ID為4的有languages中有ID為2、3、4的與之對應

多表關聯查詢我們這樣做:

func GetAllUsers(db *gorm.DB) ([]User, error) {
   var users []User
  err := db.Model(&User{}).Preload("Languages").Find(&users).Error
   return users, err
}

func GetAllLanguages(db *gorm.DB) ([]Language, error) {
   var languages []Language
  err := db.Model(&languages).Preload("Users").Find(&languages).Error
   return languages, err
}

呼叫:

users, err := GetAllUsers(db)
if err != nil {
   panic(err)
}
for _, value := range users {
   fmt.Println(value.Name)
}

languges, err := GetAllLanguages(db)
if err != nil {
   panic(err)
}
for _, value := range languges {
   fmt.Println(value.Name)
}

輸出:

2022/08/02 10:08:06 /Users/feng/go/src/GormStart/ch12/mian.go:109
[4.635ms] [rows:6] SELECT * FROM `user_languages` WHERE `user_languages`.`user_id` IN (1,2,3,4)

2022/08/02 10:08:06 /Users/feng/go/src/GormStart/ch12/mian.go:109
[2.984ms] [rows:4] SELECT * FROM `languages` WHERE `languages`.`id` IN (1,2,3,4) AND `languages`.`deleted_at` IS NULL

2022/08/02 10:08:06 /Users/feng/go/src/GormStart/ch12/mian.go:109
[54.201ms] [rows:4] SELECT * FROM `users` WHERE `users`.`deleted_at` IS NULL

ice_moss1
ice_moss2
ice_moss3

2022/08/02 10:08:06 /Users/feng/go/src/GormStart/ch12/mian.go:115
[1.467ms] [rows:6] SELECT * FROM `user_languages` WHERE `user_languages`.`language_id` IN (1,2,3,4)

2022/08/02 10:08:06 /Users/feng/go/src/GormStart/ch12/mian.go:115
[1.769ms] [rows:4] SELECT * FROM `users` WHERE `users`.`id` IN (1,2,3,4) AND `users`.`deleted_at` IS NULL

2022/08/02 10:08:06 /Users/feng/go/src/GormStart/ch12/mian.go:115
[4.931ms] [rows:4] SELECT * FROM `languages` WHERE `languages`.`deleted_at` IS NULL
golang
c++
java
本作品採用《CC 協議》,轉載必須註明作者和本文連結