• GORM CRUD 5 分钟快速上手


    1.ORM 是什么

    ORM(Object Relational Mapping),中文名为对象关系映射。

    使用 ORM 组件,可以让开发者通过操作对象的方式完成对数据库的操作(读写),避免手动书写 SQL 和完成数据到对象的转换,让我们更方便地操作数据库。

    理论上 ORM 可以让我们脱离 SQL,但实际上还是需要懂 SQL 才能更好地使用 ORM。

    2.GORM 是什么

    GORM 是一个流行的 Golang ORM 库。

    类似于 Java 生态里大家听到过的 Mybatis、Hibernate、SpringData 等。

    GORM 由国人开发,中文文档齐全,对开发者友好,支持主流关系型数据库

    • MySQL
    • SQL Server
    • PostgreSQL
    • SQlite

    GORM 功能丰富齐全:

    • 关联 (拥有一个,拥有多个,属于,多对多,多态,单表继承)
    • 钩子(before/after create/save/update/delete/find)
    • 支持 Preload、Joins 的预加载
    • 事务,嵌套事务,保存点,回滚到保存点
    • Context、预编译模式、DryRun 模式
    • 批量插入,FindInBatches,使用 Map Find/Create,使用 SQL 表达式、Context Valuer 进行 CRUD
    • SQL 构建器,Upsert,锁,Optimizer/Index/Comment Hint,命名参数,子查询
    • 复合主键,索引,约束
    • 自动迁移
    • 自定义 Logger
    • 灵活的可扩展插件 API:Database Resolver(多数据库,读写分离)、Prometheus…
    • 每个特性都经过了测试的重重考验
    • 开发者友好

    GORM 最新源码地址:go-gorm/gorm

    GORM V1 版本地址:jinzhu/gorm

    GORM 中文文档地址:这里

    本文将讲解 GORM 中常用的功能,帮助你快速上手。

    当然除了 GORM,你还有其他选择,比如 facebook-entsqlxsqlc 等。

    3.安装

    基于 Go Module 开发,import 最新包然后 go get 即可。

    go get -u gorm.io/gorm
    
    // 不同 DB 对应的驱动
    go get -u gorm.io/driver/sqlite
    go get -u gorm.io/driver/mysql
    go get -u gorm.io/driver/postgres
    go get -u gorm.io/driver/sqlserver
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    驱动包按照自己实际使用的 DB 选择即可。

    本文将以 MySQL 为例,讲解 GORM 的使用。

    4.连接 DB

    以 MySQL 为例,建立数据库连接。

    import (
    	"fmt"
    
    	"gorm.io/driver/mysql"
    	"gorm.io/gorm"
    )
    
    // MySQLConn GORM MySQL 连接。
    var MySQLConn *gorm.DB
    
    // Init gorm mysql connnection.
    // 依赖服务配置初始化完成。
    func InitMySQLConn() error {
    	// data source name.
    	dsn := fmt.Sprintf("%v:%v@tcp(%v:%v)/%v?charset=utf8mb4&parseTime=True&loc=Local", Conf.Mysql.User, Conf.Mysql.Passwd, Conf.Mysql.IP, Conf.Mysql.Port, Conf.Mysql.Dbname)
    	var err error
    	MySQLConn, err = gorm.Open(mysql.Open(dsn))
    	return err
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    填入 DB 对应的正确的用户名、密码、地址、端口、数据库名称等信息后,便可建立对应数据源的连接。相关配置一般在服务启动时,事先从配置文件中加载。

    5.创建数据表

    在进行增查改删(CRUD)之前,需要先创建一个数据表。

    GORM 中一个 struct 对应一张数据库表,对应的 struct 被称为模型。

    假如我们要创建一张商品(goods)表,那么模型可定义为:

    // Good 商品。
    type Good struct {
    	gorm.Model
    	Name  string `gorm:"type:string;size:256;not null"`
    	Price int    `gorm:"type:int;not null"`
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    其中 gorm.Model 时 GORM 预先定义的一些基础字段,我们可以嵌入直接拿来用。

    // Model a basic GoLang struct which includes the following fields: ID, CreatedAt, UpdatedAt, DeletedAt
    // It may be embedded into your model or you may build your own model without it
    //
    //	type User struct {
    //	  gorm.Model
    //	}
    type Model struct {
    	ID        uint `gorm:"primarykey"`
    	CreatedAt time.Time
    	UpdatedAt time.Time
    	DeletedAt DeletedAt `gorm:"index"`
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    字段后的 tag 用来定义字段在 DB 中的相关属性,如 primarykey 表示主键,index 表示索引,type 表示字段类型。

    除此以外,还有更加丰富的标签定义参见官方文档:字段标签

    一般在服务启动时创建数据表,如建立 DB 连接后只执行一次来完成数据表的创建。

    db.AutoMigrate(&User{})
    
    db.AutoMigrate(&User{}, &Product{}, &Order{})
    
    // 创建表时添加后缀。
    db.Set("gorm:table_options", "ENGINE=InnoDB").AutoMigrate(&User{})
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    比如创建我们上面的商品表。

    // 自动创建表,如果表已经存在不会有任何动作。
    err := MySQLConn.AutoMigrate(&Good{})
    
    • 1
    • 2

    创建好后的数据表名为 struct 名称命名方式是 snake_case(下划线命名法)的复数形式,字段名为 struct 字段的 sanke_case 形式。

    如果想更改表名,可以通过在模型结构体上添加 TableName() 方法来自定义表名称。

    func (Good) TableName() string {
    	return "tb_good"
    }
    
    • 1
    • 2
    • 3

    如果想更改表字段名,可在模型结构体上通过 gorm tag 的 column 标签指定。

    type User struct {
    	gorm.Model
    	Name string `gorm:"column:user_name"`
    }
    
    • 1
    • 2
    • 3
    • 4

    6.选择表

    通过如下方式选择要操作的表。

    DB.Model(&ModelName{})
    DB.Table("table_name")
    DB.Table("table_name alias_name")
    DB.Table("table_name AS alias_name")
    
    • 1
    • 2
    • 3
    • 4

    使用 Table() 方法为指定要操作的表时,如果表名太长,可以使用 AS(可省略)设置一个短别名来引用表。

    7.CRUD

    1.增加(Create)

    增加单个

    // 插入商品。
    task := &Good{
    	Name:   name,
    	Price: price,
    }
    err := MySQLConn.Create(task).Error
    // 或
    // err := MySQLConn.Save(task).Error
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    主键 ID 会自增,此外 GORM 还会自动维护 created_at、updated_ad 和 deleted_at 三个字段。

    其中 Save 方法在保存记录时,如果主键 ID 非空则执行更新操作,零值也会更新到 DB。如果主键 ID 为空,则执行插入操作。

    增加多个

    我们还可以使用 Create() 创建多项记录。

    users := []*User{
        User{Name: "Jinzhu", Age: 18, Birthday: time.Now()},
        User{Name: "Jackson", Age: 19, Birthday: time.Now()},
    }
    
    result := db.Create(users) // pass a slice to insert multiple row
    
    result.Error        // returns error
    result.RowsAffected // returns inserted records count
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    同样地,Save() 也可以创建多项记录。

    result := db.Save(users) // pass a slice to insert multiple row when value does not contain primary key
    
    result.Error        // returns error
    result.RowsAffected // returns inserted records count
    
    • 1
    • 2
    • 3
    • 4

    Upsert

    GORM 提供了 Upsert 的能力,记录存在(根据主键判断)则更新,不存在则增加。

    func UpsertYourModel(m *YourModel) error {
    	return Db.Save(m).Error
    }
    
    • 1
    • 2
    • 3

    或者在键冲突时决定要更新的列。

    import "gorm.io/gorm/clause"
    
    // Do nothing on conflict
    db.Clauses(clause.OnConflict{DoNothing: true}).Create(&user)
    
    // Update columns to new value on `id` conflict
    db.Clauses(clause.OnConflict{
      Columns:   []clause.Column{{Name: "id"}},
      DoUpdates: clause.AssignmentColumns([]string{"name", "age"}),
    }).Create(&users)
    
    // Update all columns to new value on conflict except primary keys and those columns having default values from sql func
    db.Clauses(clause.OnConflict{
      UpdateAll: true,
    }).Create(&users)
    
    // Update all columns to new value on composite unique index conflict.
    db.Clauses(clause.OnConflict{
      Columns:   []clause.Column{{Name: "student_no"}, {Name: "course_no"}},
      DoUpdates: clause.AssignmentColumns([]string{"status", "updated_at"}),
    }).Create(&courseSelection)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    2.查询(Read)

    按照主键查询

    db.First(&user, 10)
    // SELECT * FROM users WHERE id = 10;
    
    db.First(&user, "10")
    // SELECT * FROM users WHERE id = 10;
    
    • 1
    • 2
    • 3
    • 4
    • 5

    如果查不到,将报 gorm.ErrRecordNotFound 错误。

    当目标对象有一个主键值时,将使用主键构建查询条件,例如:

    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

    IN 查询

    比如按照多个主键查询。

    db.Find(&goods, []int{1,2,3})
    
    • 1

    或者通过内联条件。查询条件可以类似于 Where 的方式内联到 First 和 Find 等方法中。

    db.Find(&goods, "id IN ?", []int{1,2,3})
    
    • 1

    或者通过 Where 指定 IN 条件。

    db.Where("id IN ?", []int{1,2,3}).Find(&goods)
    
    • 1

    AND 条件

    再如按照其他字段进行 and 查询。

    多次调用 Where 方法可指定多个条件,条件关系为 AND。

    // getGoodsByInfo 根据商品信息分页拉取。
    func getGoodsByInfo(name string, price int, lastID uint) ([]Good, error) {
    	db := internal.MySQLConn
    	if name != "" {
    		db.Where("name = ?", name)
    	}
    	db.Where("price >= ?", price)
    	
    	// 按照每页大小 50 拉取商品。
    	db.Where("id > ?", lastID).Order("id ASC").Limit(50)
    
    	var goods []Good
    	result := db.Find(&goods)
    	return goods, result.Error
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    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

    Group 条件

    使用 Group 条件可以更轻松地编写复杂 SQL。

    db.Where(
        db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")),
    ).Or(
        db.Where("pizza = ?", "hawaiian").Where("size = ?", "xlarge"),
    ).Find(&Pizza{})
    
    // SELECT * FROM `pizzas` WHERE (pizza = "pepperoni" AND (size = "small" OR size = "medium")) OR (pizza = "hawaiian" AND size = "xlarge")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    通过结构体指定查询字段

    db.Where(&Good{Name: "衣服", Price: 10}).Find(&goods)
    // SELECT * FROM goods WHERE name = "衣服" AND price = 10;
    
    db.Where(&Good{Name: "衣服", Price: 10}, "Name").Find(&goods)
    // SELECT * FROM goods WHERE name = "衣服";
    
    • 1
    • 2
    • 3
    • 4
    • 5

    查询记录数

    // getGoodNumber 获取符合条件的商品数量。
    func getGoodNumber(price int) (int, error) {
    	var c int
    	err := internal.MySQLConn.Model(&Good{}).Where("price >= ?", price).Count(&c).Error
    	return c, err
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    查询记录是否存在

    在 GORM 中,可以使用 Count 方法来判断一个查询是否返回了记录。

    func GoodExist(name string) (bool, error) {
    	var c int64
    	err := MySQLConn.Model(&Good{}).Where("name = ?", name).Count(&count).Error
    	return count > 0, err
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    当然,你也可以使用 First 方法,并结合 ErrRecordNotFound 错误来判断记录是否存在。

    func GoodExist(name string) (bool, error) {
    	var good Good
    	err := MySQLConn.Where("name = ?", name).First(&good).Error
    	// 不存在。
    	if err == gorm.ErrRecordNotFound {
    		return false, nil
    	}
    	// 查询发生错误。
    	if err != nil {
    		return false, err
    	}
    	// 存在。
    	return true, nil
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    使用 Count 方法可以在不加载实际记录的情况下检查是否存在记录。这种方法比使用 First 方法更高效,尤其是在需要检查大量记录是否存在的情况下。因为 Count 方法只计算匹配条件的记录数,而不需要加载和返回记录的实际内容。但是,它可能会有一些微小的开销,因为它需要向数据库发送一个额外的 COUNT(*) 查询来计算记录数。

    所以,如果你只是需要检查记录是否存在,推荐使用 Count 方法。

    查询单个字段

    使用 Pluck 方法可以查询指定字段的所有值。如下面的代码查询 users 表中所有用户的姓名。

    var names []string
    db.Model(&User{}).Pluck("name", &names)
    
    • 1
    • 2

    如果是一条记录的某个字段,可以使用单个变量而非切片接收查询结果。

    var name string
    db.Model(&User{}).Where("id = ?", 1).Pluck("name", &name)
    
    • 1
    • 2

    查询多个字段

    如果您想要查询多列,您应该使用 Select 和 Scan 或 Find。

    // 超过一列的查询,应该使用 Scan 或 Find
    db.Select("name", "age").Scan(&users)
    db.Select("name", "age").Find(&users)
    
    • 1
    • 2
    • 3

    Scan 和 Find 的作用是类似的,关于二者的区别可参考 GORM Issue #4218。下面是大佬 jinzhu 的回答。

    Scan, Find using different callbacks, Scan won’t call hooks methods.

    Distinct

    从模型中选择不同的值。

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

    Distinct 也可与 Pluck 和 Count 配合使用。

    Limit & Offset

    可以使用 Limit & Offset 实现分页查询。

    Limit 指定要检索的最大记录数, Offset 指定在开始返回记录之前要跳过的记录数。

    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

    利用 Limit & Offset 实现分页查询时,如果需要同时查询符合条件的记录总数,则需要先查询记录数,再查询记录。

    db.Model(&User{}).Where("age >= ?", 18)
    
    // 先查询记录数。
    var count int64
    db.Count(&count)
    // 再查询记录。
    db.Limit(10).Offset(10).Find(&users)
    
    // 或 Count 在前。
    var count int64
    db.Count(&count).Limit(10).Offset(10).Find(&users)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    排序

    从数据库检索记录时指定顺序。

    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

    子查询

    GORM 允许您在 Table 方法中通过 FROM 子句使用子查询。

    db.Table("(?) AS u", db.Model(&User{}).Select("name", "age")).Where("age = ?", 18).Find(&User{})
    // SELECT * FROM (SELECT `name`,`age` FROM `users`) AS u WHERE `age` = 18
    
    subQuery1 := db.Model(&User{}).Select("name")
    subQuery2 := db.Model(&Pet{}).Select("name")
    db.Table("(?) AS u, (?) AS p", subQuery1, subQuery2).Find(&User{})
    // SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    当然也可以用于 JOIN。

    subQuery := db.Model(&User{}).Select("id", "name").Where("age = ?", 18)
    db.Model(&Pet{}).Select("pets.name", "users.name").Joins("LEFT JOIN (?) users ON pets.owner_id = users.id", subQuery)
    
    • 1
    • 2

    还可以用于 IN 条件。

    subQuery := db.Model(&User{}).Select("id").Where("age = ?", 18)
    db.Model(&Pet{}).Select("name").Where("owner_id IN (?)", subQuery)
    
    • 1
    • 2

    Joins

    指定联接条件。

    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

    还有很多查询方式,比如按照 map 指定查询字段以及 OR 和 NOT 条件等,具体请参考官方文档 GORM 查询

    3.更新(Update)

    更新所有字段

    使用 Save 方法更新所有字段,即使是零值也会更新。

    // 先根据 ID  查询。
    db.First(&good, 1)
    
    // 再修改值。
    good.Name = "小米"
    
    // 最后写回。
    db.Save(&user)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    更新单列

    注意,当使用 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

    更新多列

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

    // 注意:user 的 ID 是 111。
    
    // 根据 `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
    • 8
    • 9

    4.删除(Delete)

    删除一条记录

    删除一条记录时,删除对象需要指定主键,否则会触发批量 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

    根据主键删除

    GORM 允许通过主键(可以是复合主键)和内联条件来删除对象,它可以使用数字,也可以使用字符串。

    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

    批量删除

    指定的值没有主键值,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

    要有效删除大量记录,请将带有主键的切片传递给 Delete 方法。

    var users = []User{{ID: 1}, {ID: 2}, {ID: 3}}
    db.Delete(&users)
    // DELETE FROM users WHERE id IN (1,2,3);
    
    db.Delete(&users, "name LIKE ?", "%jinzhu%")
    // DELETE FROM users WHERE name LIKE "%jinzhu%" AND id IN (1,2,3); 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    软删除

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

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

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

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

    使用 Unscoped 方法查找被软删除的数据。

    db.Unscoped().Where("user_name = gry").Find(&users)
    
    • 1

    要想物理删除,使用 Unscoped 方法永久删除数据。

    user.ID = 14
    db.Unscoped().Delete(&user)
    
    • 1
    • 2

    8.关联

    1.预加载

    预加载实例

    GORM 允许使用 Preload 通过多个 SQL 中来直接加载关系。

    type User struct {
      gorm.Model
      Username string
      Orders   []Order
    }
    
    type Order struct {
      gorm.Model
      UserID uint
      Price  float64
    }
    
    // 查找 user 时预加载相关 Order
    db.Preload("Orders").Find(&users)
    // SELECT * FROM users;
    // SELECT * FROM orders WHERE user_id IN (1,2,3,4);
    
    db.Preload("Orders").Preload("Profile").Preload("Role").Find(&users)
    // SELECT * FROM users;
    // SELECT * FROM orders WHERE user_id IN (1,2,3,4); // has many
    // SELECT * FROM profiles WHERE user_id IN (1,2,3,4); // has one
    // SELECT * FROM roles WHERE id IN (4,5,6); // belongs to
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    其中一个用户可以拥有多个订单(has many),但是一个用户只能拥有一份用户资料(has one),并每个用户属于(belong to)某一个用户角色。

    预加载时,需要在模型的定义中体现这种关系,比如上面示例中 User 定义中有一个订单的切片,预加载时指定切片名称。

    条件预加载

    GORM 允许预加载时使用条件,其工作原理类似于内联条件。

    // Preload Orders with conditions
    db.Preload("Orders", "state NOT IN (?)", "cancelled").Find(&users)
    // SELECT * FROM users;
    // SELECT * FROM orders WHERE user_id IN (1,2,3,4) AND state NOT IN ('cancelled');
    
    db.Where("state = ?", "active").Preload("Orders", "state NOT IN (?)", "cancelled").Find(&users)
    // SELECT * FROM users WHERE state = 'active';
    // SELECT * FROM orders WHERE user_id IN (1,2) AND state NOT IN ('cancelled');
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    自定义预加载 SQL

    您可以通过传入func(db * gorm.DB)* gorm.DB来自定义预加载SQL,例如控制预加载内容的排序规则。

    db.Preload("Orders", func(db *gorm.DB) *gorm.DB {
      return db.Order("orders.amount DESC")
    }).Find(&users)
    // SELECT * FROM users;
    // SELECT * FROM orders WHERE user_id IN (1,2,3,4) order by orders.amount DESC;
    
    • 1
    • 2
    • 3
    • 4
    • 5

    预加载还有很多方式,比如 Joins 预加载、预加载全部和嵌套预加载等,详情请见官方文档

    9.小结

    本文简单介绍了 ORM、GORM、以及 GORM 连接数据库,创建数据表和 CRUD 的简单操作,帮忙新手快速上手。

    更多用法,请参见官方文档 GORM 指南,这里有你想要的一切。

    如果您喜欢这篇文章,欢迎关注我的微信公众号“恋喵大鲤鱼”了解最新精彩内容。


    参考文献

    GORM 指南| GORM - GORM
    GORM 极速入门- 卢振千的博客
    19-Gorm入门到精通- 刘清政 - 博客园
    Go组件学习——gorm四步带你搞定DB增删改查 - 掘金

  • 相关阅读:
    《bug记录》在利用TCP协议创建【服务器-客户端交互程序】中出现的一些问题
    【MyBatis源码分析】六、MyBatis Plugins(拦截器)
    结构方程模型调整
    基于springboot实现校园在线拍卖系统项目【项目源码】计算机毕业设计
    微服务和Spring Cloud Alibaba介绍
    【新知实验室 TRTC&IM】实时互动课堂最佳实践
    基于Springboot的社区志愿者服务管理系统
    每日一题 300最长递增子序列(贪心+二分)(灵神模版)
    网页翻译软件-网页自动采集翻译软件免费
    .NET餐厅管理系统sql数据帮助类执行对单个Entity的更新(这个可以添加额外的约束条件)
  • 原文地址:https://blog.csdn.net/K346K346/article/details/128005404