推荐阅读官方教程

安装

和 gin 一样, 使用前需要先使用以下命令安装:

go get -u gorm.io/gorm

本篇以 MySQL 举例, 所以还需要安装 MySQL 的驱动:

go get -u gorm.io/driver/mysql

连接

连接数据库只需要一句话即可:

dsn := "user:password@tcp(127.0.0.1:3306)/db?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
	panic(err)
}

建表

连接以后可以使用 AutoMigrate 创建数据表:

db.AutoMigrate(&User{}) // User是一个结构体

User 定义如下:

type User struct {
	gorm.Model
	Name     string
	Age      int `gorm:"default:12"` // 表示默认值为12
	Salary   float32
	Birthday time.Time
}

gorm.Model 是 gorm 官方模型, 其定义如下:

type Model struct {
	ID        uint `gorm:"primarykey"`
	CreatedAt time.Time
	UpdatedAt time.Time
	DeletedAt DeletedAt `gorm:"index"`
}

gorm.Model 中的值会在修改数据库时自动填充

gorm 默认表名为结构体的蛇形复数, 字段名为蛇形命名, id 为自增主键, 比如上述模型的数据表为:

你也可以自定义表名:

type Tabler interface {
    TableName() string
}

// TableName 会将 User 的表名重写为 `profiles`
func (User) TableName() string {
  return "profiles"
}

除了上面展示的 default 标签, 你也可以自定义以下标签:

增删改查

增加

gorm 可以单个增加:

user := User{Name: "张三", Age: 13, Salary: 12.0, Birthday: time.Now()}
db.Debug().Create(&user) // 打印sql语句

也可以增加多个:

users := make([]User, 0)
users = append(users, User{Name: "张三", Salary: 12.0, Birthday: time.Now()})
users = append(users, User{Name: "张三", Salary: 15.0, Birthday: time.Now()})
users = append(users, User{Name: "李四", Salary: 15.0, Birthday: time.Now()})
db.Debug().Create(&users)

但是注意: 当结构体中字段值为空或者零时, 此时插入数据库则会填入默认值

user := User{Name: "张三", Age: 0, Salary: 12.0, Birthday: time.Now()}
db.Debug().Create(&user)

比如上述代码打印的 sql 语句如下:

可以看到图中插入的 sql 语句在 Age 处的值为默认值 12, 并不是我们想要的 0, 针对此问题我们有两种方法解决:

  1. 使用指针类型变量

    将字段类型改为指针, 结构体定义如下:

    type User struct {
        gorm.Model
        Name     string
        Age      *int `gorm:"default:12"`
        Salary   float32
        Birthday time.Time
    }
    

    go 语句如下:

    // 指针类型的零值需要使用 new()
    user := User{Name: "张三", Age: new(int), Salary: 12.0, Birthday: time.Now()}
    db.Debug().Create(&user)
    

    sql 语句如下:

    可以看到 Age 字段的值变为了 0

  2. 使用 Scanner/Valuer 接口

    结构体定义如下:

    type User struct {
    	gorm.Model
    	Name     string
    	Age      sql.NullInt32 `gorm:"default:12"`
    	Salary   float32
    	Birthday time.Time
    }
    

    可以查看 sql.NullInt32 的定义如下, 可以知道这是一个结构体, 当 Validtrue 时, 使用 Int32 的值; 否则认为传入的值为空, 使用默认值

    // NullInt32 represents an int32 that may be null.
    // NullInt32 implements the Scanner interface so
    // it can be used as a scan destination, similar to NullString.
    type NullInt32 struct {
    	Int32 int32
    	Valid bool // Valid is true if Int32 is not NULL
    }
    
    // Scan implements the Scanner interface.
    func (n *NullInt32) Scan(value any) error {
    	if value == nil {
    		n.Int32, n.Valid = 0, false
    		return nil
    	}
    	n.Valid = true
    	return convertAssign(&n.Int32, value)
    }
    
    // Value implements the driver Valuer interface.
    func (n NullInt32) Value() (driver.Value, error) {
    	if !n.Valid {
    		return nil, nil
    	}
    	return int64(n.Int32), nil
    }
    

    go 语句如下:

    user := User{Name: "张三", Age: sql.NullInt32{0, true}, Salary: 12.0, Birthday: time.Now()}
    db.Debug().Create(&user)
    

    sql 语句如下:

删除

当你的结构体定义中有 go.Model 时, 此时你将获取软删除功能, 你的删除操作都是将 delete_at 设定为删除事件, 当你执行查询操作时会默认加上一个 delete_al is null 的条件

// 删除单个 user的id作为条件
// UPDATE users SET deleted_at="2023-07-01 17:03" WHERE id = 67;
db.Delete(&user)

// 批量删除
// UPDATE users SET deleted_at="2023-07-01 17:13" WHERE age = 15;
db.Where("age = ?", 15).Delete(&User{})

// 查询记录时会忽略被软删除的记录
// SELECT * FROM users WHERE age = 15 AND deleted_at IS NULL;
db.Where("age = 15").Find(&user)

// Unscoped 方法可以查询被软删除的记录
// SELECT * FROM users WHERE age = 15;
db.Unscoped().Where("age = 15").Find(&users)

// 永久删除 user的id作为条件
// DELETE FROM orders WHERE id=78;
db.Unscoped().Delete(&user)

修改

更新所有字段

db.First(&user)
user.Name = "李四"
*user.Age = 17
// UPDATE `users` SET `created_at`='2023-07-01 17:58:57.788',`updated_at`='2023-07-01 17:58:57.803',`deleted_at`=NULL,`name`='李四',`age`=17,`salary`=12.000000,`birthday`='2023-07-01 17:58:57.788' WHERE `users`.`deleted_at` IS NULL AND `id` = 199
db.Debug().Save(&user)
db.First(&user)

更新部分字段

// 更新单个属性,如果它有变化
// UPDATE `users` SET `name`='赵六',`updated_at`='2023-07-01 17:58:57.805' WHERE `users`.`deleted_at` IS NULL AND `id` = 199
db.Debug().Model(&user).Update("name", "赵六")
// 根据给定的条件更新单个属性
// UPDATE `users` SET `name`='赵六',`updated_at`='2023-07-01 17:58:57.807' WHERE age = 12 AND `users`.`deleted_at` IS NULL AND `id` = 199
db.Debug().Model(&user).Where("age = ?", 12).Update("name", "赵六")
// 使用 map 更新多个属性,只会更新其中有变化的属性
// UPDATE `users` SET `age`=18,`name`='张三',`updated_at`='2023-07-01 17:58:57.808' WHERE `users`.`deleted_at` IS NULL AND `id` = 199
db.Debug().Model(&user).Updates(map[string]interface{}{"name": "张三", "age": 18})

更新选定字段

// UPDATE `users` SET `name`='赵六',`updated_at`='2023-07-01 17:58:57.809' WHERE `users`.`deleted_at` IS NULL AND `id` = 199
db.Debug().Model(&user).Select("name").Updates(map[string]interface{}{"name": "赵六", "age": 17})
//  UPDATE `users` SET `age`=17,`updated_at`='2023-07-01 17:58:57.811' WHERE `users`.`deleted_at` IS NULL AND `id` = 199
db.Debug().Model(&user).Omit("name").Updates(map[string]interface{}{"name": "赵六", "age": 17})

批量更新

// UPDATE `users` SET `age`=18,`name`='李' WHERE id IN (151,155)
db.Debug().Table("users").Where("id IN (?)", []int{151, 155}).Updates(map[string]interface{}{"name": "李", "age": 18})
// 使用 struct 更新时,只会更新非零值字段,若想更新所有字段,请使用map[string]interface{}
// UPDATE `users` SET `updated_at`='2023-07-01 17:58:57.816',`name`='赵六' WHERE 1 = 1 AND `users`.`deleted_at` IS NULL
db.Debug().Model(User{}).Where("1 = 1").Updates(User{Name: "赵六"})
// 使用 `RowsAffected` 获取更新记录总数
// UPDATE `users` SET `updated_at`='2023-07-01 17:58:57.818',`name`='赵六' WHERE 1 = 1 AND `users`.`deleted_at` IS NULL
rows := db.Debug().Model(User{}).Where("1 = 1").Updates(User{Name: "赵六"}).RowsAffected
fmt.Println(rows)

查询

查询条件

String 条件
// SELECT * FROM users WHERE name = '张三' ORDER BY id LIMIT 1;
db.Where("name = ?", "张三").First(&user)
// SELECT * FROM users WHERE name = '张三' ORDER BY id;
db.Where("name = ?", "张三").Find(&users)
Struct 和 Map 条件
// Struct
// SELECT * FROM users WHERE name = "张三" AND age = 15 ORDER BY id LIMIT 1;
age := 15
db.Debug().Where(&User{Name: "张三", Age: &age}).First(&user)
// Map
// SELECT * FROM users WHERE name = "张三" AND age = 15;
db.Where(map[string]interface{}{"name": "张三", "age": 15}).Find(&users)
// 主键切片条件
// SELECT * FROM users WHERE id IN (20, 21, 22);
db.Where([]int64{20, 21, 22}).Find(&users)
内联条件
// SELECT * FROM users WHERE name = "李四";
db.Find(&user, "name = ?", "李四")
NOT 条件
// SELECT * FROM users WHERE NOT name = "张三" ORDER BY id LIMIT 1;
db.Not("name = ?", "张三").First(&user)

// Not In
// SELECT * FROM users WHERE name NOT IN ("张三", "李四");
db.Not(map[string]interface{}{"name": []string{"张三", "李四"}}).Find(&users)
OR 条件
// SELECT * FROM users WHERE name = '李四' OR name = '王五';
db.Where("name = ?", "李四").Or("name = ?", "王五").Find(&users)

选取特定字段

// SELECT name, age FROM users;
db.Select("name", "age").Find(&users)

其他还有诸如 limit , offset, group 等查询就不一一列举, 详情可以查看官网的 查询高级查询

总结

增加用 Create() 就行, 删除修改和查询实际上都是使用 Where() 加附加条件, 最后再分别调用 Delete(), Update(), Updates(), First(), Find() 执行最终操作即可

程序清单

package main

import (
	"fmt"
	"time"

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

type User struct {
	gorm.Model
	Name     string
	Age      *int `gorm:"default:12"`
	Salary   float32
	Birthday time.Time
}

func main() {
    // 修改你的 用户名 密码 连接地址 数据库名
	dsn := "user:password@tcp(127.0.0.1:3306)/db?charset=utf8mb4&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		panic(err)
	}

	db.AutoMigrate(&User{})

	// 增加

	// 单个增加
	user := User{Name: "张三", Age: new(int), Salary: 12.0, Birthday: time.Now()}
	db.Create(&user)

	// 增加多个
	users := make([]User, 0)
	users = append(users, User{Name: "张三", Salary: 12.0, Birthday: time.Now()})
	users = append(users, User{Name: "李四", Salary: 15.0, Birthday: time.Now()})
	users = append(users, User{Name: "王五", Salary: 15.0, Birthday: time.Now()})
	db.Create(&users)

	// 修改

	// 更新所有字段
	db.First(&user)
	user.Name = "李四"
	*user.Age = 17
	// UPDATE `users` SET `created_at`='2023-07-01 17:58:57.788',`updated_at`='2023-07-01 17:58:57.803',`deleted_at`=NULL,`name`='李四',`age`=17,`salary`=12.000000,`birthday`='2023-07-01 17:58:57.788' WHERE `users`.`deleted_at` IS NULL AND `id` = 199
	db.Save(&user)
	db.First(&user)

	// 更新部分字段
	// 更新单个属性,如果它有变化
	// UPDATE `users` SET `name`='赵六',`updated_at`='2023-07-01 17:58:57.805' WHERE `users`.`deleted_at` IS NULL AND `id` = 199
	db.Model(&user).Update("name", "赵六")
	// 根据给定的条件更新单个属性
	// UPDATE `users` SET `name`='赵六',`updated_at`='2023-07-01 17:58:57.807' WHERE age = 12 AND `users`.`deleted_at` IS NULL AND `id` = 199
	db.Model(&user).Where("age = ?", 12).Update("name", "赵六")
	// 使用 map 更新多个属性,只会更新其中有变化的属性
	// UPDATE `users` SET `age`=18,`name`='张三',`updated_at`='2023-07-01 17:58:57.808' WHERE `users`.`deleted_at` IS NULL AND `id` = 199
	db.Model(&user).Updates(map[string]interface{}{"name": "张三", "age": 18})

	// 更新选定字段
	// UPDATE `users` SET `name`='赵六',`updated_at`='2023-07-01 17:58:57.809' WHERE `users`.`deleted_at` IS NULL AND `id` = 199
	db.Model(&user).Select("name").Updates(map[string]interface{}{"name": "赵六", "age": 17})
	//  UPDATE `users` SET `age`=17,`updated_at`='2023-07-01 17:58:57.811' WHERE `users`.`deleted_at` IS NULL AND `id` = 199
	db.Model(&user).Omit("name").Updates(map[string]interface{}{"name": "赵六", "age": 17})

	// 批量更新
	// UPDATE `users` SET `age`=18,`name`='李' WHERE id IN (151,155)
	db.Table("users").Where("id IN (?)", []int{151, 155}).Updates(map[string]interface{}{"name": "李", "age": 18})
	// 使用 struct 更新时,只会更新非零值字段,若想更新所有字段,请使用map[string]interface{}
	// UPDATE `users` SET `updated_at`='2023-07-01 17:58:57.816',`name`='赵六' WHERE 1 = 1 AND `users`.`deleted_at` IS NULL
	db.Model(User{}).Where("1 = 1").Updates(User{Name: "赵六"})
	// 使用 `RowsAffected` 获取更新记录总数
	// UPDATE `users` SET `updated_at`='2023-07-01 17:58:57.818',`name`='赵六' WHERE 1 = 1 AND `users`.`deleted_at` IS NULL
	rows := db.Model(User{}).Where("1 = 1").Updates(User{Name: "赵六"}).RowsAffected
	fmt.Println(rows)

	// 查询

	// SELECT * FROM users WHERE name = '张三' ORDER BY id LIMIT 1;
	user = User{}
	db.Where("name = ?", "张三").First(&user)
	// SELECT * FROM users WHERE name = '张三' ORDER BY id;
	user = User{}
	db.Where("name = ?", "张三").Find(&users)

	// Struct
	// SELECT * FROM users WHERE name = "张三" AND age = 15 ORDER BY id LIMIT 1;
	age := 12
	user = User{}
	db.Where(&User{Name: "张三", Age: &age}).First(&user)
	// Map
	// SELECT * FROM users WHERE name = "张三" AND age = 15;
	db.Where(map[string]interface{}{"name": "张三", "age": 15}).Find(&users)
	// 主键切片条件
	// SELECT * FROM users WHERE id IN (20, 21, 22);
	db.Where([]int64{20, 21, 22}).Find(&users)

	// SELECT * FROM users WHERE name = "李四";
	user = User{}
	db.Find(&user, "name = ?", "李四")

	// SELECT * FROM users WHERE NOT name = "张三" ORDER BY id LIMIT 1;
	user = User{}
	db.Not("name = ?", "张三").First(&user)
	// Not In
	// SELECT * FROM users WHERE name NOT IN ("张三", "李四");
	db.Not(map[string]interface{}{"name": []string{"张三", "李四"}}).Find(&users)

	// SELECT * FROM users WHERE name = '张三' OR name = '李四';
	db.Where("name = ?", "张三").Or("name = ?", "李四").Find(&users)

	// SELECT name, age FROM users;
	db.Select("name", "age").Find(&users)

	// 删除

	// 删除单个 user里的值当作条件
	// UPDATE users SET deleted_at="2023-07-01 17:03" WHERE id = 67;
	db.Delete(&user)
	// 批量删除
	// UPDATE users SET deleted_at="2023-07-01 17:13" WHERE age = 15;
	db.Where("age = ?", 15).Delete(&User{})

	// 查询记录时会忽略被软删除的记录
	// SELECT * FROM users WHERE age = 15 AND deleted_at IS NULL;
	db.Where("age = 15").Find(&user)

	// Unscoped 方法可以查询被软删除的记录
	// SELECT * FROM users WHERE age = 15;
	db.Unscoped().Where("age = 15").Find(&users)

	// 永久删除
	// DELETE FROM orders WHERE id=78;
	db.Unscoped().Delete(&user)

	// 删库跑路
	db.Unscoped().Where("1 = 1").Delete(&User{})
}

参考文章

gorm 官方教程

GORM CRUD 指南