Go语言学习笔记—gorm(二)

时间:2024-10-10 14:01:31

文章目录

  • 一 CRUD 接口—gorm创建
    • 1.1 创建表
    • 1.2 创建记录
    • 1.3 用指定字段创建记录
    • 1.4 批量插入
    • 1.5 创建钩子
    • 1.6 根据 Map 创建
    • 1.7 关联创建
    • 1.8 默认值
  • 二 CRUD 接口—gorm查询
    • 2.1 检索单个对象
    • 2.2 用主键检索
    • 2.3 检索全部对象
    • 2.4 条件
      • 2.4.1 String 条件
      • 2.4.2 Struct & Map 条件
      • 2.4.3 指定结构体查询字段
      • 2.4.4 内联条件
      • 2.4.5 Not 条件
      • 2.4.6 Or 条件
    • 2.5 选择特定字段
    • 2.6 Order
    • 2.7 Limit & Offset
    • 2.8 Group By & Having
    • 2.9 Distinct
    • 2.10 Joins
      • 2.10.1 Joins 预加载
      • 2.10.2 Joins a Derived Table
    • 2.11 Scan
  • 三 CRUD 接口—gorm更新
    • 3.1 保存所有字段
    • 3.2 更新单个列
    • 3.3 更新多列
    • 3.4 更新选定字段
    • 3.5 更新 Hook
    • 3.6 批量更新
      • 3.6.1 阻止全局更新
      • 3.6.2 更新的记录数
    • 3.7 高级选项
      • 3.7.1 使用 SQL 表达式更新
      • 3.7.2 根据子查询进行更新
      • 3.7.3 不使用 Hook 和时间追踪
      • 3.7.4 返回修改行的数据
      • 3.7.5 检查字段是否有变更?
      • 3.7.6 在 Update 时修改值
  • 四 CRUD 接口—gorm删除
    • 4.1 删除一条记录
    • 4.2 根据主键删除
    • 4.3 Delete Hook
    • 4.4 批量删除
      • 4.4.1 阻止全局删除
      • 4.4.2 返回删除行的数据
    • 4.5 软删除
      • 4.5.1 查找被软删除的记录
      • 4.5.2 永久删除
      • 4.5.4 Delete Flag
        • Unix Second
        • Use `1` / `0` AS Delete Flag
        • Mixed Mode
  • 五 CRUD 接口—gorm原生SQL和SQL生成器
    • 5.1 原生 SQL
    • 5.2 命名参数
    • 5.3 DryRun 模式
    • 5.4 ToSQL
    • 5.5 `Row` & `Rows`
    • 5.6 将 `` 扫描至 model
    • 5.7 Connection
    • 5.8 Advanced
      • 5.8.1 子句(Clause)
      • 5.8.2 子句构造器
      • 5.8.3 子句选项
      • 5.8.4 StatementModifier


一 CRUD 接口—gorm创建

1.1 创建表

package main

import (
	"time"

	"/driver/mysql"
	"/gorm"
)

var db *gorm.DB

func init() {
   
	dsn := "root:960690@tcp(127.0.0.1:3306)/golang_db?charset=utf8mb4&parseTime=True&loc=Local"
	d, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
   })
	if err != nil {
   
		panic("failed to connect database")
	}

	db = d
}

type User struct {
   
	gorm.Model
	Name     string
	Age      int
	Birthday time.Time
}

var user = User{
   
	Name:     "Jinzhu",
	Age:      18,
	Birthday: time.Now(),
}

func creatTable() {
   
	db.AutoMigrate(&User{
   })
}

func main() {
   
	creatTable()
}

运行结果:

在这里插入图片描述

1.2 创建记录

result := db.Create(&user) // 通过数据的指针来创建

fmt.Printf(":%v", user.ID)
fmt.Printf("插入记录的条数:%v", result.RowsAffected)

运行结果:

user.ID:1插入记录的条数:1

mysql中查看:

在这里插入图片描述

1.3 用指定字段创建记录

  • 创建记录并更新给出的字段。

    user := User{
         
    	Name:     "Jinzhu",
    	Age:      18,
    	Birthday: time.Now(),
    }
    db.Select("Name", "Age", "CreatedAt").Create(&user)
    

    mysql中查看:

    在这里插入图片描述

  • 创建一个记录且一同忽略传递给略去的字段值。

    db.Omit("Name", "Age", "CreatedAt").Create(&user)
    

    mysql中查看:

    在这里插入图片描述

1.4 批量插入

要有效地插入大量记录,请将一个 slice 传递给 Create 方法。 GORM 将生成单独一条SQL语句来插入所有数据,并回填主键的值,钩子方法也会被调用。

var users = []User{
   {
   Name: "jinzhu1"}, {
   Name: "jinzhu2"}, {
   Name: "jinzhu3"}}
db.Create(&users)

for _, user := range users {
   
  user.ID // 1,2,3
}

使用 CreateInBatches 分批创建时,你可以指定每批的数量,例如:

var users = []User{
   {
   name: "jinzhu_1"}, ...., {
   Name: "jinzhu_10000"}}

// 数量为 100
db.CreateInBatches(users, 100)

Upsert 和 Create With Associations 也支持批量插入

注意 使用CreateBatchSize 选项初始化 GORM 时,所有的创建& 关联 INSERT 都将遵循该选项

db, err := gorm.Open(sqlite.Open(""), &gorm.Config{
   
  CreateBatchSize: 1000,
})

db := db.Session(&gorm.Session{
   CreateBatchSize: 1000})

users = [5000]User{
   {
   Name: "jinzhu", Pets: []Pet{
   pet1, pet2, pet3}}...}

db.Create(&users)
// INSERT INTO users xxx (5 batches)
// INSERT INTO pets xxx (15 batches)

1.5 创建钩子

GORM 允许用户定义的钩子有 BeforeSave, BeforeCreate, AfterSave, AfterCreate 创建记录时将调用这些钩子方法,请参考 Hooks 中关于生命周期的详细信息

func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
   
  u.UUID = uuid.New()

    if u.Role == "admin" {
   
        return errors.New("invalid role")
    }
    return
}

如果您想跳过 钩子 方法,您可以使用 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)

1.6 根据 Map 创建

GORM 支持根据 map[string]interface{}[]map[string]interface{}{} 创建记录,例如:

db.Model(&User{
   }).Create(map[string]interface{
   }{
   
  "Name": "jinzhu", "Age": 18,
})

// batch insert from `[]map[string]interface{}{}`
db.Model(&User{
   }).Create([]map[string]interface{
   }{
   
  {
   "Name": "jinzhu_1", "Age": 18},
  {
   "Name": "jinzhu_2", "Age": 20},
})

注意: 根据 map 创建记录时,association 不会被调用,且主键也不会自动填充

1.7 关联创建

创建关联数据时,如果关联值是非零值,这些关联会被 upsert,且它们的 Hook 方法也会被调用

type CreditCard struct {
   
  gorm.Model
  Number   string
  UserID   uint
}

type User struct {
   
  gorm.Model
  Name       string
  CreditCard CreditCard
}

db.Create(&User{
   
  Name: "jinzhu",
  CreditCard: CreditCard{
   Number: "411111111111"}
})
// INSERT INTO `users` ...
// INSERT INTO `credit_cards` ...

您也可以通过 SelectOmit 跳过关联保存,例如:

db.Omit("CreditCard").Create(&user)

// 跳过所有关联
db.Omit(clause.Associations).Create(&user)

1.8 默认值

您可以通过标签 default 为字段定义默认值,如:

type User struct {
   
  ID   int64
  Name string `gorm:"default:galeone"`
  Age  int64  `gorm:"default:18"`
}

插入记录到数据库时,默认值 会被用于 填充值为 零值 的字段

注意 对于声明了默认值的字段,像 0''false 等零值是不会保存到数据库。您需要使用指针类型或 Scanner/Valuer 来避免这个问题,例如:

type User struct {
   
  gorm.Model
  Name string
  Age  *int           `gorm:"default:18"`
  Active sql.NullBool `gorm:"default:true"`
}

注意 若要数据库有默认、虚拟/生成的值,你必须为字段设置 default 标签。若要在迁移时跳过默认值定义,你可以使用 default:(-),例如:

type User struct {
   
  ID        string `gorm:"default:uuid_generate_v3()"` // db func
  FirstName string
  LastName  string
  Age       uint8
  FullName  string `gorm:"->;type:GENERATED ALWAYS AS (concat(firstname,' ',lastname));default:(-);"`
}

使用虚拟/生成的值时,你可能需要禁用它的创建、更新权限

二 CRUD 接口—gorm查询

2.1 检索单个对象

GORM 提供了 FirstTakeLast 方法,以便从数据库中检索单个对象。当查询数据库时它添加了 LIMIT 1 条件,且没有找到记录时,它会返回 ErrRecordNotFound 错误

// 获取第一条记录(主键升序)
db.First(&user)
// SELECT * FROM users ORDER BY id LIMIT 1;

// 获取一条记录,没有指定排序字段
db.Take(&user)
// SELECT * FROM users LIMIT 1;

// 获取最后一条记录(主键降序)
db.Last(&user)
// SELECT * FROM users ORDER BY id DESC LIMIT 1;

result := db.First(&user)
result.RowsAffected // 返回找到的记录数
result.Error        // returns error or nil

// 检查 ErrRecordNotFound 错误
errors.Is(result.Error, gorm.ErrRecordNotFound)

如果你想避免ErrRecordNotFound错误,你可以使用Find,比如(1).Find(&user)Find方法可以接受struct和slice的数据。

FirstLast 会根据主键排序,分别查询第一条和最后一条记录。 只有在目标 struct 是指针或者通过 () 指定 model 时,该方法才有效。 此外,如果相关 model 没有定义主键,那么将按 model 的第一个字段进行排序。 例如:

var user User
var users []User

// works because destination struct is passed in
db.First(&user)
// SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1

// works because model is specified using `()`
result := map[string]interface{
   }{
   }
db.Model(&User{
   }).First(&result)
// SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1

// doesn't work
result := map[string]interface{
   }{
   }
db.Table("users").First(&result)

// works with Take
result := map[string]interface{
   }{
   }
db.Table("users").Take(&result)

// no primary key defined, results will be ordered by first field (., `Code`)
type Language struct {
   
  Code string
  Name string
}
db.First(&Language{
   })
// SELECT * FROM `languages` ORDER BY `languages`.`code` LIMIT 1

2.2 用主键检索

如果主键是数字类型,您可以使用 内联条件 来检索对象。 传入字符串参数时,需要特别注意 SQL 注入问题,查看 安全 获取详情.

db.First(&user, 10)
// SELECT * FROM users WHERE id = 10;

db.First(&user, "10")
// SELECT * FROM users WHERE id = 10;

db.Find(&users, []int{
   1,2,3})
// SELECT * FROM users WHERE id IN (1,2,3);

如果主键是字符串(例如像 uuid),查询将被写成这样:

db.First(&user, "id = ?", "1b74413f-f3b8-409f-ac47-e8c062e3472a")
// SELECT * FROM users WHERE id = "1b74413f-f3b8-409f-ac47-e8c062e3472a";

When the destination object has a primary value, the primary key will be used to build the condition, for example:

var user = User{
   ID: 10}
db.First(&user)
// SELECT * FROM users WHERE id = 10;

var result User
db.Model(User{
   ID: 10}).First(&result)
// SELECT * FROM users WHERE id = 10;

2.3 检索全部对象

// Get all records
result := db.Find(&users)
// SELECT * FROM users;

result.RowsAffected // returns found records count, equals `len(users)`
result.Error        // returns error

2.4 条件

2.4.1 String 条件

// Get first matched record
db.Where("name = ?", "jinzhu").First(&user)
// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;

// Get all matched records
db.Where("name <> ?", "jinzhu").Find(&users)
// SELECT * FROM users WHERE name <> 'jinzhu';

// IN
db.Where("name IN ?", []string{
   "jinzhu", "jinzhu 2"}).Find(&users)
// SELECT * FROM users WHERE name IN ('jinzhu','jinzhu 2');

// LIKE
db.Where("name LIKE ?", "%jin%").Find(&users)
// SELECT * FROM users WHERE name LIKE '%jin%';

// AND
db.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' AND age >= 22;

// Time
db.Where("updated_at > ?", lastWeek).Find(&users)
// SELECT * FROM users WHERE updated_at > '2000-01-01 00:00:00';

// BETWEEN
db.Where("created_at BETWEEN ? AND ?", lastWeek, today)