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


    文章目录


    一 CRUD 接口—gorm创建

    1.1 创建表

    package main
    
    import (
    	"time"
    
    	"gorm.io/driver/mysql"
    	"gorm.io/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
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    运行结果:

    在这里插入图片描述

    1.2 创建记录

    result := db.Create(&user) // 通过数据的指针来创建
    
    fmt.Printf("user.ID:%v", user.ID)
    fmt.Printf("插入记录的条数:%v", result.RowsAffected)
    
    • 1
    • 2
    • 3
    • 4

    运行结果:

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

    mysql中查看:

    在这里插入图片描述

    1.3 用指定字段创建记录

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

      user := User{
      	Name:     "Jinzhu",
      	Age:      18,
      	Birthday: time.Now(),
      }
      db.Select("Name", "Age", "CreatedAt").Create(&user)
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6

      mysql中查看:

      在这里插入图片描述

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

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

      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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

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

    var users = []User{{name: "jinzhu_1"}, ...., {Name: "jinzhu_10000"}}
    
    // 数量为 100
    db.CreateInBatches(users, 100)
    
    • 1
    • 2
    • 3
    • 4

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

    注意 使用CreateBatchSize 选项初始化 GORM 时,所有的创建& 关联 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)
    // INSERT INTO users xxx (5 batches)
    // INSERT INTO pets xxx (15 batches)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    如果您想跳过 钩子 方法,您可以使用 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
    • 2
    • 3
    • 4
    • 5

    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},
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    注意: 根据 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` ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

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

    db.Omit("CreditCard").Create(&user)
    
    // 跳过所有关联
    db.Omit(clause.Associations).Create(&user)
    
    • 1
    • 2
    • 3
    • 4

    1.8 默认值

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

    type User struct {
      ID   int64
      Name string `gorm:"default:galeone"`
      Age  int64  `gorm:"default:18"`
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

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

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

    type User struct {
      gorm.Model
      Name string
      Age  *int           `gorm:"default:18"`
      Active sql.NullBool `gorm:"default:true"`
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    注意 若要数据库有默认、虚拟/生成的值,你必须为字段设置 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:(-);"`
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

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

    二 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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

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

    FirstLast 会根据主键排序,分别查询第一条和最后一条记录。 只有在目标 struct 是指针或者通过 db.Model() 指定 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 `db.Model()`
    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 (i.e., `Code`)
    type Language struct {
      Code string
      Name string
    }
    db.First(&Language{})
    // SELECT * FROM `languages` ORDER BY `languages`.`code` LIMIT 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

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

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

    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    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).Find(&users)
    // SELECT * FROM users WHERE created_at BETWEEN '2000-01-01 00:00:00' AND '2000-01-08 00:00:00';
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    2.4.2 Struct & Map 条件

    // Struct
    db.Where(&User{Name: "jinzhu", Age: 20}).First(&user)
    // SELECT * FROM users WHERE name = "jinzhu" AND age = 20 ORDER BY id LIMIT 1;
    
    // Map
    db.Where(map[string]interface{}{"name": "jinzhu", "age": 20}).Find(&users)
    // SELECT * FROM users WHERE name = "jinzhu" AND age = 20;
    
    // Slice of primary keys
    db.Where([]int64{20, 21, 22}).Find(&users)
    // SELECT * FROM users WHERE id IN (20, 21, 22);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    NOTE When querying with struct, GORM will only query with non-zero fields, that means if your field’s value is 0, '', false or other zero values, it won’t be used to build query conditions, for example:

    db.Where(&User{Name: "jinzhu", Age: 0}).Find(&users)
    // SELECT * FROM users WHERE name = "jinzhu";
    
    • 1
    • 2

    To include zero values in the query conditions, you can use a map, which will include all key-values as query conditions, for example:

    db.Where(map[string]interface{}{"Name": "jinzhu", "Age": 0}).Find(&users)
    // SELECT * FROM users WHERE name = "jinzhu" AND age = 0;
    
    • 1
    • 2

    For more details, see Specify Struct search fields.

    2.4.3 指定结构体查询字段

    When searching with struct, you can specify which particular values from the struct to use in the query conditions by passing in the relevant field name or the dbname to Where(), for example:

    db.Where(&User{Name: "jinzhu"}, "name", "Age").Find(&users)
    // SELECT * FROM users WHERE name = "jinzhu" AND age = 0;
    
    db.Where(&User{Name: "jinzhu"}, "Age").Find(&users)
    // SELECT * FROM users WHERE age = 0;
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2.4.4 内联条件

    Query conditions can be inlined into methods like First and Find in a similar way to Where.

    // Get by primary key if it were a non-integer type
    db.First(&user, "id = ?", "string_primary_key")
    // SELECT * FROM users WHERE id = 'string_primary_key';
    
    // Plain SQL
    db.Find(&user, "name = ?", "jinzhu")
    // SELECT * FROM users WHERE name = "jinzhu";
    
    db.Find(&users, "name <> ? AND age > ?", "jinzhu", 20)
    // SELECT * FROM users WHERE name <> "jinzhu" AND age > 20;
    
    // Struct
    db.Find(&users, User{Age: 20})
    // SELECT * FROM users WHERE age = 20;
    
    // Map
    db.Find(&users, map[string]interface{}{"age": 20})
    // SELECT * FROM users WHERE age = 20;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    2.4.5 Not 条件

    Build NOT conditions, works similar to Where

    db.Not("name = ?", "jinzhu").First(&user)
    // SELECT * FROM users WHERE NOT name = "jinzhu" ORDER BY id LIMIT 1;
    
    // Not In
    db.Not(map[string]interface{}{"name": []string{"jinzhu", "jinzhu 2"}}).Find(&users)
    // SELECT * FROM users WHERE name NOT IN ("jinzhu", "jinzhu 2");
    
    // Struct
    db.Not(User{Name: "jinzhu", Age: 18}).First(&user)
    // SELECT * FROM users WHERE name <> "jinzhu" AND age <> 18 ORDER BY id LIMIT 1;
    
    // Not In slice of primary keys
    db.Not([]int64{1,2,3}).First(&user)
    // SELECT * FROM users WHERE id NOT IN (1,2,3) ORDER BY id LIMIT 1;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    2.4.6 Or 条件

    db.Where("role = ?", "admin").Or("role = ?", "super_admin").Find(&users)
    // SELECT * FROM users WHERE role = 'admin' OR role = 'super_admin';
    
    // Struct
    db.Where("name = 'jinzhu'").Or(User{Name: "jinzhu 2", Age: 18}).Find(&users)
    // SELECT * FROM users WHERE name = 'jinzhu' OR (name = 'jinzhu 2' AND age = 18);
    
    // Map
    db.Where("name = 'jinzhu'").Or(map[string]interface{}{"name": "jinzhu 2", "age": 18}).Find(&users)
    // SELECT * FROM users WHERE name = 'jinzhu' OR (name = 'jinzhu 2' AND age = 18);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    For more complicated SQL queries. please also refer to Group Conditions in Advanced Query.

    2.5 选择特定字段

    Select allows you to specify the fields that you want to retrieve from database. Otherwise, GORM will select all fields by default.

    db.Select("name", "age").Find(&users)
    // SELECT name, age FROM users;
    
    db.Select([]string{"name", "age"}).Find(&users)
    // SELECT name, age FROM users;
    
    db.Table("users").Select("COALESCE(age,?)", 42).Rows()
    // SELECT COALESCE(age,'42') FROM users;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    Also check out Smart Select Fields

    2.6 Order

    Specify order when retrieving records from the database

    db.Order("age desc, name").Find(&users)
    // SELECT * FROM users ORDER BY age desc, name;
    
    // Multiple orders
    db.Order("age desc").Order("name").Find(&users)
    // SELECT * FROM users ORDER BY age desc, name;
    
    db.Clauses(clause.OrderBy{
      Expression: clause.Expr{SQL: "FIELD(id,?)", Vars: []interface{}{[]int{1, 2, 3}}, WithoutParentheses: true},
    }).Find(&User{})
    // SELECT * FROM users ORDER BY FIELD(id,1,2,3)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    2.7 Limit & Offset

    Limit specify the max number of records to retrieve Offset specify the number of records to skip before starting to return the records

    db.Limit(3).Find(&users)
    // SELECT * FROM users LIMIT 3;
    
    // Cancel limit condition with -1
    db.Limit(10).Find(&users1).Limit(-1).Find(&users2)
    // SELECT * FROM users LIMIT 10; (users1)
    // SELECT * FROM users; (users2)
    
    db.Offset(3).Find(&users)
    // SELECT * FROM users OFFSET 3;
    
    db.Limit(10).Offset(5).Find(&users)
    // SELECT * FROM users OFFSET 5 LIMIT 10;
    
    // Cancel offset condition with -1
    db.Offset(10).Find(&users1).Offset(-1).Find(&users2)
    // SELECT * FROM users OFFSET 10; (users1)
    // SELECT * FROM users; (users2)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    Refer to Pagination for details on how to make a paginator

    2.8 Group By & Having

    type result struct {
      Date  time.Time
      Total int
    }
    
    db.Model(&User{}).Select("name, sum(age) as total").Where("name LIKE ?", "group%").Group("name").First(&result)
    // SELECT name, sum(age) as total FROM `users` WHERE name LIKE "group%" GROUP BY `name` LIMIT 1
    
    
    db.Model(&User{}).Select("name, sum(age) as total").Group("name").Having("name = ?", "group").Find(&result)
    // SELECT name, sum(age) as total FROM `users` GROUP BY `name` HAVING name = "group"
    
    rows, err := db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Rows()
    defer rows.Close()
    for rows.Next() {
      ...
    }
    
    rows, err := db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Having("sum(amount) > ?", 100).Rows()
    defer rows.Close()
    for rows.Next() {
      ...
    }
    
    type Result struct {
      Date  time.Time
      Total int64
    }
    db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Having("sum(amount) > ?", 100).Scan(&results)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    2.9 Distinct

    Selecting distinct values from the model

    db.Distinct("name", "age").Order("name, age desc").Find(&results)
    
    • 1

    Distinct works with Pluck and Count too

    2.10 Joins

    Specify Joins conditions

    type result struct {
      Name  string
      Email string
    }
    
    db.Model(&User{}).Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Scan(&result{})
    // SELECT users.name, emails.email FROM `users` left join emails on emails.user_id = users.id
    
    rows, err := db.Table("users").Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Rows()
    for rows.Next() {
      ...
    }
    
    db.Table("users").Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Scan(&results)
    
    // multiple joins with parameter
    db.Joins("JOIN emails ON emails.user_id = users.id AND emails.email = ?", "jinzhu@example.org").Joins("JOIN credit_cards ON credit_cards.user_id = users.id").Where("credit_cards.number = ?", "411111111111").Find(&user)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    2.10.1 Joins 预加载

    You can use Joins eager loading associations with a single SQL, for example:

    db.Joins("Company").Find(&users)
    // SELECT `users`.`id`,`users`.`name`,`users`.`age`,`Company`.`id` AS `Company__id`,`Company`.`name` AS `Company__name` FROM `users` LEFT JOIN `companies` AS `Company` ON `users`.`company_id` = `Company`.`id`;
    
    • 1
    • 2

    Join with conditions

    db.Joins("Company", db.Where(&Company{Alive: true})).Find(&users)
    // SELECT `users`.`id`,`users`.`name`,`users`.`age`,`Company`.`id` AS `Company__id`,`Company`.`name` AS `Company__name` FROM `users` LEFT JOIN `companies` AS `Company` ON `users`.`company_id` = `Company`.`id` AND `Company`.`alive` = true;
    
    • 1
    • 2

    For more details, please refer to Preloading (Eager Loading).

    2.10.2 Joins a Derived Table

    You can also use Joins to join a derived table.

    type User struct {
        Id  int
        Age int
    }
    
    type Order struct {
        UserId     int
        FinishedAt *time.Time
    }
    
    query := db.Table("order").Select("MAX(order.finished_at) as latest").Joins("left join user user on order.user_id = user.id").Where("user.age > ?", 18).Group("order.user_id")
    db.Model(&Order{}).Joins("join (?) q on order.finished_at = q.latest", query).Scan(&results)
    // SELECT `order`.`user_id`,`order`.`finished_at` FROM `order` join (SELECT MAX(order.finished_at) as latest FROM `order` left join user user on order.user_id = user.id WHERE user.age > 18 GROUP BY `order`.`user_id`) q on order.finished_at = q.latest
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    2.11 Scan

    Scanning results into a struct works similarly to the way we use Find

    type Result struct {
      Name string
      Age  int
    }
    
    var result Result
    db.Table("users").Select("name", "age").Where("name = ?", "Antonio").Scan(&result)
    
    // Raw SQL
    db.Raw("SELECT name, age FROM users WHERE name = ?", "Antonio").Scan(&result)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    三 CRUD 接口—gorm更新

    3.1 保存所有字段

    Save 会保存所有的字段,即使字段是零值

    db.First(&user)
    
    user.Name = "jinzhu 2"
    user.Age = 100
    db.Save(&user)
    // UPDATE users SET name='jinzhu 2', age=100, birthday='2016-01-01', updated_at = '2013-11-17 21:34:10' WHERE id=111;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    3.2 更新单个列

    当使用 Update 更新单个列时,你需要指定条件,否则会返回 ErrMissingWhereClause 错误,查看 Block Global Updates 获取详情。当使用了 Model 方法,且该对象主键有值,该值会被用于构建条件,例如:

    // 条件更新
    db.Model(&User{}).Where("active = ?", true).Update("name", "hello")
    // UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE active=true;
    
    // User 的 ID 是 `111`
    db.Model(&user).Update("name", "hello")
    // UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;
    
    // 根据条件和 model 的值进行更新
    db.Model(&user).Where("active = ?", true).Update("name", "hello")
    // UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111 AND active=true;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    3.3 更新多列

    Updates 方法支持 structmap[string]interface{} 参数。当使用 struct 更新时,默认情况下,GORM 只会更新非零值的字段

    // 根据 `struct` 更新属性,只会更新非零值的字段
    db.Model(&user).Updates(User{Name: "hello", Age: 18, Active: false})
    // UPDATE users SET name='hello', age=18, updated_at = '2013-11-17 21:34:10' WHERE id = 111;
    
    // 根据 `map` 更新属性
    db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
    // UPDATE users SET name='hello', age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    注意 当通过 struct 更新时,GORM 只会更新非零字段。 如果您想确保指定字段被更新,你应该使用 Select 更新选定字段,或使用 map 来完成更新操作

    3.4 更新选定字段

    如果您想要在更新时选定、忽略某些字段,您可以使用 SelectOmit

    // 使用 Map 进行 Select
    // User's ID is `111`:
    db.Model(&user).Select("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
    // UPDATE users SET name='hello' WHERE id=111;
    
    db.Model(&user).Omit("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
    // UPDATE users SET age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;
    
    // 使用 Struct 进行 Select(会 select 零值的字段)
    db.Model(&user).Select("Name", "Age").Updates(User{Name: "new_name", Age: 0})
    // UPDATE users SET name='new_name', age=0 WHERE id=111;
    
    // Select 所有字段(查询包括零值字段的所有字段)
    db.Model(&user).Select("*").Update(User{Name: "jinzhu", Role: "admin", Age: 0})
    
    // Select 除 Role 外的所有字段(包括零值字段的所有字段)
    db.Model(&user).Select("*").Omit("Role").Update(User{Name: "jinzhu", Role: "admin", Age: 0})
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    3.5 更新 Hook

    对于更新操作,GORM 支持 BeforeSaveBeforeUpdateAfterSaveAfterUpdate 钩子,这些方法将在更新记录时被调用,详情请参阅 钩子

    func (u *User) BeforeUpdate(tx *gorm.DB) (err error) {
        if u.Role == "admin" {
            return errors.New("admin user not allowed to update")
        }
        return
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    3.6 批量更新

    如果您尚未通过 Model 指定记录的主键,则 GORM 会执行批量更新

    // 根据 struct 更新
    db.Model(User{}).Where("role = ?", "admin").Updates(User{Name: "hello", Age: 18})
    // UPDATE users SET name='hello', age=18 WHERE role = 'admin';
    
    // 根据 map 更新
    db.Table("users").Where("id IN ?", []int{10, 11}).Updates(map[string]interface{}{"name": "hello", "age": 18})
    // UPDATE users SET name='hello', age=18 WHERE id IN (10, 11);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3.6.1 阻止全局更新

    如果在没有任何条件的情况下执行批量更新,默认情况下,GORM 不会执行该操作,并返回 ErrMissingWhereClause 错误

    对此,你必须加一些条件,或者使用原生 SQL,或者启用 AllowGlobalUpdate 模式,例如:

    db.Model(&User{}).Update("name", "jinzhu").Error // gorm.ErrMissingWhereClause
    
    db.Model(&User{}).Where("1 = 1").Update("name", "jinzhu")
    // UPDATE users SET `name` = "jinzhu" WHERE 1=1
    
    db.Exec("UPDATE users SET name = ?", "jinzhu")
    // UPDATE users SET name = "jinzhu"
    
    db.Session(&gorm.Session{AllowGlobalUpdate: true}).Model(&User{}).Update("name", "jinzhu")
    // UPDATE users SET `name` = "jinzhu"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    3.6.2 更新的记录数

    获取受更新影响的行数

    // 通过 `RowsAffected` 得到更新的记录数
    result := db.Model(User{}).Where("role = ?", "admin").Updates(User{Name: "hello", Age: 18})
    // UPDATE users SET name='hello', age=18 WHERE role = 'admin';
    
    result.RowsAffected // 更新的记录数
    result.Error        // 更新的错误
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    3.7 高级选项

    3.7.1 使用 SQL 表达式更新

    GORM 允许使用 SQL 表达式更新列,例如:

    // product 的 ID 是 `3`
    db.Model(&product).Update("price", gorm.Expr("price * ? + ?", 2, 100))
    // UPDATE "products" SET "price" = price * 2 + 100, "updated_at" = '2013-11-17 21:34:10' WHERE "id" = 3;
    
    db.Model(&product).Updates(map[string]interface{}{"price": gorm.Expr("price * ? + ?", 2, 100)})
    // UPDATE "products" SET "price" = price * 2 + 100, "updated_at" = '2013-11-17 21:34:10' WHERE "id" = 3;
    
    db.Model(&product).UpdateColumn("quantity", gorm.Expr("quantity - ?", 1))
    // UPDATE "products" SET "quantity" = quantity - 1 WHERE "id" = 3;
    
    db.Model(&product).Where("quantity > 1").UpdateColumn("quantity", gorm.Expr("quantity - ?", 1))
    // UPDATE "products" SET "quantity" = quantity - 1 WHERE "id" = 3 AND quantity > 1;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    并且 GORM 也允许使用 SQL 表达式、自定义数据类型的 Context Valuer 来更新,例如:

    // 根据自定义数据类型创建
    type Location struct {
        X, Y int
    }
    
    func (loc Location) GormValue(ctx context.Context, db *gorm.DB) clause.Expr {
      return clause.Expr{
        SQL:  "ST_PointFromText(?)",
        Vars: []interface{}{fmt.Sprintf("POINT(%d %d)", loc.X, loc.Y)},
      }
    }
    
    db.Model(&User{ID: 1}).Updates(User{
      Name:  "jinzhu",
      Location: Location{X: 100, Y: 100},
    })
    // UPDATE `user_with_points` SET `name`="jinzhu",`location`=ST_PointFromText("POINT(100 100)") WHERE `id` = 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    3.7.2 根据子查询进行更新

    使用子查询更新表

    db.Model(&user).Update("company_name", db.Model(&Company{}).Select("name").Where("companies.id = users.company_id"))
    // UPDATE "users" SET "company_name" = (SELECT name FROM companies WHERE companies.id = users.company_id);
    
    db.Table("users as u").Where("name = ?", "jinzhu").Update("company_name", db.Table("companies as c").Select("name").Where("c.id = u.company_id"))
    
    db.Table("users as u").Where("name = ?", "jinzhu").Updates(map[string]interface{}{}{"company_name": db.Table("companies as c").Select("name").Where("c.id = u.company_id")})
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    3.7.3 不使用 Hook 和时间追踪

    如果您想在更新时跳过 Hook 方法且不追踪更新时间,可以使用 UpdateColumnUpdateColumns,其用法类似于 UpdateUpdates

    // 更新单个列
    db.Model(&user).UpdateColumn("name", "hello")
    // UPDATE users SET name='hello' WHERE id = 111;
    
    // 更新多个列
    db.Model(&user).UpdateColumns(User{Name: "hello", Age: 18})
    // UPDATE users SET name='hello', age=18 WHERE id = 111;
    
    // 更新选中的列
    db.Model(&user).Select("name", "age").UpdateColumns(User{Name: "hello", Age: 0})
    // UPDATE users SET name='hello', age=0 WHERE id = 111;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    3.7.4 返回修改行的数据

    返回被修改的数据,仅适用于支持 Returning 的数据库,例如:

    // 返回所有列
    var users []User
    DB.Model(&users).Clauses(clause.Returning{}).Where("role = ?", "admin").Update("salary", gorm.Expr("salary * ?", 2))
    // UPDATE `users` SET `salary`=salary * 2,`updated_at`="2021-10-28 17:37:23.19" WHERE role = "admin" RETURNING *
    // users => []User{{ID: 1, Name: "jinzhu", Role: "admin", Salary: 100}, {ID: 2, Name: "jinzhu.2", Role: "admin", Salary: 1000}}
    
    // 返回指定的列
    DB.Model(&users).Clauses(clause.Returning{Columns: []clause.Column{{Name: "name"}, {Name: "salary"}}}).Where("role = ?", "admin").Update("salary", gorm.Expr("salary * ?", 2))
    // UPDATE `users` SET `salary`=salary * 2,`updated_at`="2021-10-28 17:37:23.19" WHERE role = "admin" RETURNING `name`, `salary`
    // users => []User{{ID: 0, Name: "jinzhu", Role: "", Salary: 100}, {ID: 0, Name: "jinzhu.2", Role: "", Salary: 1000}}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    3.7.5 检查字段是否有变更?

    GORM 提供了 Changed 方法,它可以被用在 Before Update Hook 里,它会返回字段是否有变更的布尔值

    Changed 方法只能与 UpdateUpdates 方法一起使用,并且它只是检查 Model 对象字段的值与 UpdateUpdates 的值是否相等,如果值有变更,且字段没有被忽略,则返回 true

    func (u *User) BeforeUpdate(tx *gorm.DB) (err error) {
      // 如果 Role 字段有变更
        if tx.Statement.Changed("Role") {
        return errors.New("role not allowed to change")
        }
    
      if tx.Statement.Changed("Name", "Admin") { // 如果 Name 或 Role 字段有变更
        tx.Statement.SetColumn("Age", 18)
      }
    
      // 如果任意字段有变更
        if tx.Statement.Changed() {
            tx.Statement.SetColumn("RefreshedAt", time.Now())
        }
        return nil
    }
    
    db.Model(&User{ID: 1, Name: "jinzhu"}).Updates(map[string]interface{"name": "jinzhu2"})
    // Changed("Name") => true
    db.Model(&User{ID: 1, Name: "jinzhu"}).Updates(map[string]interface{"name": "jinzhu"})
    // Changed("Name") => false, 因为 `Name` 没有变更
    db.Model(&User{ID: 1, Name: "jinzhu"}).Select("Admin").Updates(map[string]interface{
      "name": "jinzhu2", "admin": false,
    })
    // Changed("Name") => false, 因为 `Name` 没有被 Select 选中并更新
    
    db.Model(&User{ID: 1, Name: "jinzhu"}).Updates(User{Name: "jinzhu2"})
    // Changed("Name") => true
    db.Model(&User{ID: 1, Name: "jinzhu"}).Updates(User{Name: "jinzhu"})
    // Changed("Name") => false, 因为 `Name` 没有变更
    db.Model(&User{ID: 1, Name: "jinzhu"}).Select("Admin").Updates(User{Name: "jinzhu2"})
    // Changed("Name") => false, 因为 `Name` 没有被 Select 选中并更新
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    3.7.6 在 Update 时修改值

    若要在 Before 钩子中改变要更新的值,如果它是一个完整的更新,可以使用 Save;否则,应该使用 SetColumn ,例如:

    func (user *User) BeforeSave(tx *gorm.DB) (err error) {
      if pw, err := bcrypt.GenerateFromPassword(user.Password, 0); err == nil {
        tx.Statement.SetColumn("EncryptedPassword", pw)
      }
    
      if tx.Statement.Changed("Code") {
        user.Age += 20
        tx.Statement.SetColumn("Age", user.Age)
      }
    }
    
    db.Model(&user).Update("Name", "jinzhu")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    四 CRUD 接口—gorm删除

    4.1 删除一条记录

    删除一条记录时,删除对象需要指定主键,否则会触发 批量 Delete,例如:

    // Email 的 ID 是 `10`
    db.Delete(&email)
    // DELETE from emails where id = 10;
    // 软删除
    
    // 带额外条件的删除
    db.Where("name = ?", "jinzhu").Delete(&email)
    // DELETE from emails where id = 10 AND name = "jinzhu";
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    4.2 根据主键删除

    GORM 允许通过主键(可以是复合主键)和内联条件来删除对象,它可以使用数字(如以下例子。也可以使用字符串——译者注)。查看 查询-内联条件(Query Inline Conditions) 了解详情。

    db.Delete(&User{}, 10)
    // DELETE FROM users WHERE id = 10;
    
    db.Delete(&User{}, "10")
    // DELETE FROM users WHERE id = 10;
    
    db.Delete(&users, []int{1,2,3})
    // DELETE FROM users WHERE id IN (1,2,3);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    4.3 Delete Hook

    对于删除操作,GORM 支持 BeforeDeleteAfterDelete Hook,在删除记录时会调用这些方法,查看 Hook 获取详情

    func (u *User) BeforeDelete(tx *gorm.DB) (err error) {
        if u.Role == "admin" {
            return errors.New("admin user not allowed to delete")
        }
        return
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    4.4 批量删除

    如果指定的值不包括主属性,那么 GORM 会执行批量删除,它将删除所有匹配的记录

    db.Where("email LIKE ?", "%jinzhu%").Delete(&Email{})
    // DELETE from emails where email LIKE "%jinzhu%";
    
    db.Delete(&Email{}, "email LIKE ?", "%jinzhu%")
    // DELETE from emails where email LIKE "%jinzhu%";
    
    • 1
    • 2
    • 3
    • 4
    • 5

    4.4.1 阻止全局删除

    如果在没有任何条件的情况下执行批量删除,GORM 不会执行该操作,并返回 ErrMissingWhereClause 错误

    对此,你必须加一些条件,或者使用原生 SQL,或者启用 AllowGlobalUpdate 模式,例如:

    db.Delete(&User{}).Error // gorm.ErrMissingWhereClause
    
    db.Where("1 = 1").Delete(&User{})
    // DELETE FROM `users` WHERE 1=1
    
    db.Exec("DELETE FROM users")
    // DELETE FROM users
    
    db.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(&User{})
    // DELETE FROM users
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    4.4.2 返回删除行的数据

    返回被删除的数据,仅适用于支持 Returning 的数据库,例如:

    // 返回所有列
    var users []User
    DB.Clauses(clause.Returning{}).Where("role = ?", "admin").Delete(&users)
    // DELETE FROM `users` WHERE role = "admin" RETURNING *
    // users => []User{{ID: 1, Name: "jinzhu", Role: "admin", Salary: 100}, {ID: 2, Name: "jinzhu.2", Role: "admin", Salary: 1000}}
    
    // 返回指定的列
    DB.Clauses(clause.Returning{Columns: []clause.Column{{Name: "name"}, {Name: "salary"}}}).Where("role = ?", "admin").Delete(&users)
    // DELETE FROM `users` WHERE role = "admin" RETURNING `name`, `salary`
    // users => []User{{ID: 0, Name: "jinzhu", Role: "", Salary: 100}, {ID: 0, Name: "jinzhu.2", Role: "", Salary: 1000}}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    4.5 软删除

    如果您的模型包含了一个 gorm.deletedat 字段(gorm.Model 已经包含了该字段),它将自动获得软删除的能力!

    拥有软删除能力的模型调用 Delete 时,记录不会从数据库中被真正删除。但 GORM 会将 DeletedAt 置为当前时间, 并且你不能再通过普通的查询方法找到该记录。

    // user 的 ID 是 `111`
    db.Delete(&user)
    // UPDATE users SET deleted_at="2013-10-29 10:23" WHERE id = 111;
    
    // 批量删除
    db.Where("age = ?", 20).Delete(&User{})
    // UPDATE users SET deleted_at="2013-10-29 10:23" WHERE age = 20;
    
    // 在查询时会忽略被软删除的记录
    db.Where("age = 20").Find(&user)
    // SELECT * FROM users WHERE age = 20 AND deleted_at IS NULL;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    如果您不想引入 gorm.Model,您也可以这样启用软删除特性:

    type User struct {
      ID      int
      Deleted gorm.DeletedAt
      Name    string
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    4.5.1 查找被软删除的记录

    您可以使用 Unscoped 找到被软删除的记录

    db.Unscoped().Where("age = 20").Find(&users)
    // SELECT * FROM users WHERE age = 20;
    
    • 1
    • 2

    4.5.2 永久删除

    您也可以使用 Unscoped 永久删除匹配的记录

    db.Unscoped().Delete(&order)
    // DELETE FROM orders WHERE id=10;
    
    • 1
    • 2

    4.5.4 Delete Flag

    By default, gorm.Model uses *time.Time as the value for the DeletedAt field, and it provides other data formats support with plugin gorm.io/plugin/soft_delete

    INFO when creating unique composite index for the DeletedAt field, you must use other data format like unix second/flag with plugin gorm.io/plugin/soft_delete‘s help, e.g:

    import "gorm.io/plugin/soft_delete"
    
    type User struct {
    ID        uint
    Name      string                `gorm:"uniqueIndex:udx_name"`
    DeletedAt soft_delete.DeletedAt `gorm:"uniqueIndex:udx_name"`
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    Unix Second

    Use unix second as delete flag

    import "gorm.io/plugin/soft_delete"
    
    type User struct {
      ID        uint
      Name      string
      DeletedAt soft_delete.DeletedAt
    }
    
    // Query
    SELECT * FROM users WHERE deleted_at = 0;
    
    // Delete
    UPDATE users SET deleted_at = /* current unix second */ WHERE ID = 1;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    You can also specify to use milli or nano seconds as the value, for example:

    type User struct {
      ID    uint
      Name  string
      DeletedAt soft_delete.DeletedAt `gorm:"softDelete:milli"`
      // DeletedAt soft_delete.DeletedAt `gorm:"softDelete:nano"`
    }
    
    // Query
    SELECT * FROM users WHERE deleted_at = 0;
    
    // Delete
    UPDATE users SET deleted_at = /* current unix milli second or nano second */ WHERE ID = 1;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    Use 1 / 0 AS Delete Flag

    import "gorm.io/plugin/soft_delete"
    
    type User struct {
      ID    uint
      Name  string
      IsDel soft_delete.DeletedAt `gorm:"softDelete:flag"`
    }
    
    // Query
    SELECT * FROM users WHERE is_del = 0;
    
    // Delete
    UPDATE users SET is_del = 1 WHERE ID = 1;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    Mixed Mode

    Mixed mode can use 0, 1 or unix seconds to mark data as deleted or not, and save the deleted time at the same time.

    type User struct {
      ID        uint
      Name      string
      DeletedAt time.Time
      IsDel     soft_delete.DeletedAt `gorm:"softDelete:flag,DeletedAtField:DeletedAt"` // use `1` `0`
      // IsDel     soft_delete.DeletedAt `gorm:"softDelete:,DeletedAtField:DeletedAt"` // use `unix second`
      // IsDel     soft_delete.DeletedAt `gorm:"softDelete:nano,DeletedAtField:DeletedAt"` // use `unix nano second`
    }
    
    // Query
    SELECT * FROM users WHERE is_del = 0;
    
    // Delete
    UPDATE users SET is_del = 1, deleted_at = /* current unix second */ WHERE ID = 1;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    五 CRUD 接口—gorm原生SQL和SQL生成器

    5.1 原生 SQL

    原生查询 SQL 和 Scan

    type Result struct {
      ID   int
      Name string
      Age  int
    }
    
    var result Result
    db.Raw("SELECT id, name, age FROM users WHERE name = ?", 3).Scan(&result)
    
    db.Raw("SELECT id, name, age FROM users WHERE name = ?", 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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    Exec 原生 SQL

    db.Exec("DROP TABLE users")
    db.Exec("UPDATE orders SET shipped_at = ? WHERE id IN ?", time.Now(), []int64{1, 2, 3})
    
    // Exec with SQL Expression
    db.Exec("UPDATE users SET money = ? WHERE name = ?", gorm.Expr("money * ? + ?", 10000, 1), "jinzhu")
    
    • 1
    • 2
    • 3
    • 4
    • 5

    注意 GORM 允许缓存预编译 SQL 语句来提高性能,查看 性能 获取详情

    5.2 命名参数

    GORM 支持 sql.NamedArgmap[string]interface{}{} 或 struct 形式的命名参数,例如:

    db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user)
    // SELECT * FROM `users` WHERE name1 = "jinzhu" OR name2 = "jinzhu"
    
    db.Where("name1 = @name OR name2 = @name", map[string]interface{}{"name": "jinzhu2"}).First(&result3)
    // SELECT * FROM `users` WHERE name1 = "jinzhu2" OR name2 = "jinzhu2" ORDER BY `users`.`id` LIMIT 1
    
    // 原生 SQL 及命名参数
    db.Raw("SELECT * FROM users WHERE name1 = @name OR name2 = @name2 OR name3 = @name",
       sql.Named("name", "jinzhu1"), sql.Named("name2", "jinzhu2")).Find(&user)
    // SELECT * FROM users WHERE name1 = "jinzhu1" OR name2 = "jinzhu2" OR name3 = "jinzhu1"
    
    db.Exec("UPDATE users SET name1 = @name, name2 = @name2, name3 = @name",
       sql.Named("name", "jinzhunew"), sql.Named("name2", "jinzhunew2"))
    // UPDATE users SET name1 = "jinzhunew", name2 = "jinzhunew2", name3 = "jinzhunew"
    
    db.Raw("SELECT * FROM users WHERE (name1 = @name AND name3 = @name) AND name2 = @name2",
       map[string]interface{}{"name": "jinzhu", "name2": "jinzhu2"}).Find(&user)
    // SELECT * FROM users WHERE (name1 = "jinzhu" AND name3 = "jinzhu") AND name2 = "jinzhu2"
    
    type NamedArgument struct {
        Name string
        Name2 string
    }
    
    db.Raw("SELECT * FROM users WHERE (name1 = @Name AND name3 = @Name) AND name2 = @Name2",
         NamedArgument{Name: "jinzhu", Name2: "jinzhu2"}).Find(&user)
    // SELECT * FROM users WHERE (name1 = "jinzhu" AND name3 = "jinzhu") AND name2 = "jinzhu2"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    5.3 DryRun 模式

    在不执行的情况下生成 SQL 及其参数,可以用于准备或测试生成的 SQL,详情请参考 Session

    stmt := db.Session(&Session{DryRun: true}).First(&user, 1).Statement
    stmt.SQL.String() //=> SELECT * FROM `users` WHERE `id` = $1 ORDER BY `id`
    stmt.Vars         //=> []interface{}{1}
    
    • 1
    • 2
    • 3

    5.4 ToSQL

    返回生成的 SQL 但不执行。

    GORM使用 database/sql 的参数占位符来构建 SQL 语句,它会自动转义参数以避免 SQL 注入,但我们不保证生成 SQL 的安全,请只用于调试。

    sql := DB.ToSQL(func(tx *gorm.DB) *gorm.DB {
      return tx.Model(&User{}).Where("id = ?", 100).Limit(10).Order("age desc").Find(&[]User{})
    })
    sql //=> SELECT * FROM "users" WHERE id = 100 AND "users"."deleted_at" IS NULL ORDER BY age desc LIMIT 10
    
    • 1
    • 2
    • 3
    • 4

    5.5 Row & Rows

    获取 *sql.Row 结果

    // 使用 GORM API 构建 SQL
    row := db.Table("users").Where("name = ?", "jinzhu").Select("name", "age").Row()
    row.Scan(&name, &age)
    
    // 使用原生 SQL
    row := db.Raw("select name, age, email from users where name = ?", "jinzhu").Row()
    row.Scan(&name, &age, &email)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    获取 *sql.Rows 结果

    // 使用 GORM API 构建 SQL
    rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Select("name, age, email").Rows()
    defer rows.Close()
    for rows.Next() {
      rows.Scan(&name, &age, &email)
    
      // 业务逻辑...
    }
    
    // 原生 SQL
    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)
    
      // 业务逻辑...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    转到 FindInBatches 获取如何在批量中查询和处理记录的信息, 转到 Group 条件 获取如何构建复杂 SQL 查询的信息

    5.6 将 sql.Rows 扫描至 model

    使用 ScanRows 将一行记录扫描至 struct,例如:

    rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Select("name, age, email").Rows() // (*sql.Rows, error)
    defer rows.Close()
    
    var user User
    for rows.Next() {
      // ScanRows 将一行扫描至 user
      db.ScanRows(rows, &user)
    
      // 业务逻辑...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    5.7 Connection

    Run mutliple SQL in same db tcp connection (not in a transaction)

    db.Connection(func(tx *gorm.DB) error {
      tx.Exec("SET my.role = ?", "admin")
    
      tx.First(&User{})
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5

    5.8 Advanced

    5.8.1 子句(Clause)

    GORM uses SQL builder generates SQL internally, for each operation, GORM creates a *gorm.Statement object, all GORM APIs add/change Clause for the Statement, at last, GORM generated SQL based on those clauses

    For example, when querying with First, it adds the following clauses to the Statement

    clause.Select{Columns: "*"}
    clause.From{Tables: clause.CurrentTable}
    clause.Limit{Limit: 1}
    clause.OrderByColumn{
      Column: clause.Column{Table: clause.CurrentTable, Name: clause.PrimaryKey},
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    Then GORM build finally querying SQL in the Query callbacks like:

    Statement.Build("SELECT", "FROM", "WHERE", "GROUP BY", "ORDER BY", "LIMIT", "FOR")
    
    • 1

    Which generate SQL:

    SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1
    
    • 1

    You can define your own Clause and use it with GORM, it needs to implements Interface

    Check out examples for reference

    5.8.2 子句构造器

    For different databases, Clauses may generate different SQL, for example:

    db.Offset(10).Limit(5).Find(&users)
    // Generated for SQL Server
    // SELECT * FROM "users" OFFSET 10 ROW FETCH NEXT 5 ROWS ONLY
    // Generated for MySQL
    // SELECT * FROM `users` LIMIT 5 OFFSET 10
    
    • 1
    • 2
    • 3
    • 4
    • 5

    Which is supported because GORM allows database driver register Clause Builder to replace the default one, take the Limit as example

    5.8.3 子句选项

    GORM defined Many Clauses, and some clauses provide advanced options can be used for your application

    Although most of them are rarely used, if you find GORM public API can’t match your requirements, may be good to check them out, for example:

    db.Clauses(clause.Insert{Modifier: "IGNORE"}).Create(&user)
    // INSERT IGNORE INTO users (name,age...) VALUES ("jinzhu",18...);
    
    • 1
    • 2

    5.8.4 StatementModifier

    GORM provides interface StatementModifier allows you modify statement to match your requirements, take Hints as example

    import "gorm.io/hints"
    
    db.Clauses(hints.New("hint")).Find(&User{})
    // SELECT * /*+ hint */ FROM `users`
    
    • 1
    • 2
    • 3
    • 4
  • 相关阅读:
    Android如何连接metamask并签名
    千字长文 | 学习编程这么多年,聊一聊Java和Go
    JavaEE:多线程(3):案例代码
    npm命令大全
    实践和项目:解决实际问题时,选择合适的数据结构和算法
    【C++】类和对象(上),三种类对象模型,全局变量和静态变量在.h中的问题,类的内存对齐等等..快来看看
    头一次见单例模式讲的如此透彻
    HDRUNet: Single Image HDR Reconstruction withDenoising and Dequantization
    java数组
    【自然语言处理(NLP)】基于循环神经网络实现情感分类
  • 原文地址:https://blog.csdn.net/qq_39280718/article/details/126302481