Gorm 快速入门 | 青训营笔记

390 阅读8分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的第4篇笔记

GORM

Quick Start

连接数据库

package main

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

func main() {
	// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情

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

使用 logger 打印日志信息, 设置 Info 的日志级别

package main

import (
	"log"
	"os"
	"time"

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

func main() {
	// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情

	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,        // 启用彩色打印
		},
	)

	dsn := "root:1@tcp(127.0.0.1:3306)/huanxi?charset=utf8mb4&parseTime=True&loc=Local"
	_, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
		Logger: newLogger,
	})
	if err != nil {
		panic(err)
	}
}

创建模型 自动迁移

声明模型时, 按照约定数据库的名称会变为结构体的复数, 例如, Tag 在数据库中为 tags. 使用 TableName 覆盖约定.

package main

import (
	"fmt"
	"log"
	"os"
	"time"

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

type Tag struct {
	gorm.Model
	Name string `json:"name"`
}

/* 等价与
type Tag struct {
	ID        uint `gorm:"primarykey"`
	CreatedAt time.Time
	UpdatedAt time.Time
	DeletedAt DeletedAt `gorm:"index"`
	Name string `json:"name"`
}*/

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

func init() {
	// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情

	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,        // 启用彩色打印
		},
	)

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

	err = db.AutoMigrate(Tag{})
	if err != nil {
		panic("AutoMigrate Failed")
	}

}

func main() {
	fmt.Println("Init finished")
}

GORM 的字段标签, 大小写不敏感

标签名 说明
column 指定 db 列名
type 列数据类型,推荐使用兼容性好的通用类型,例如:所有数据库都支持 bool、int、uint、float、string、time、bytes 并且可以和其他标签一起使用,例如:not nullsize, autoIncrement… 像 varbinary(8) 这样指定数据库数据类型也是支持的。在使用指定数据库数据类型时,它需要是完整的数据库数据类型,如:MEDIUMINT UNSIGNED not NULL AUTO_INCREMENT
size 指定列大小,例如:size:256
primaryKey 指定列为主键
unique 指定列为唯一
default 指定列的默认值
precision 指定列的精度
scale 指定列大小
not null 指定列为 NOT NULL
autoIncrement 指定列为自动增长
autoIncrementIncrement 自动步长,控制连续记录之间的间隔
embedded 嵌套字段
embeddedPrefix 嵌入字段的列名前缀
autoCreateTime 创建时追踪当前时间,对于 int 字段,它会追踪秒级时间戳,您可以使用 nano/milli 来追踪纳秒、毫秒时间戳,例如:autoCreateTime:nano
autoUpdateTime 创建/更新时追踪当前时间,对于 int 字段,它会追踪秒级时间戳,您可以使用 nano/milli 来追踪纳秒、毫秒时间戳,例如:autoUpdateTime:milli
index 根据参数创建索引,多个字段使用相同的名称则创建复合索引,查看 索引 获取详情
uniqueIndex index 相同,但创建的是唯一索引
check 创建检查约束,例如 check:age > 13,查看 约束 获取详情
<- 设置字段写入的权限, <-:create 只创建、<-:update 只更新、<-:false 无写入权限、<- 创建和更新权限
-> 设置字段读的权限,->:false 无读权限
- 忽略该字段,- 无读写权限
comment 迁移时为字段添加注释

插入

package main

import (
	"fmt"
	"log"
	"os"
	"time"

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

type Tag struct {
	gorm.Model
	Name string `json:"name"`
}

/* 等价与
type Tag struct {
	ID        uint `gorm:"primarykey"`
	CreatedAt time.Time
	UpdatedAt time.Time
	DeletedAt DeletedAt `gorm:"index"`
	Name string `json:"name"`
}*/

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

var db *gorm.DB
var err error

func init() {
	// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情

	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,        // 启用彩色打印
		},
	)

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

	err = db.AutoMigrate(Tag{})
	if err != nil {
		panic("AutoMigrate Failed")
	}

}

func main() {
	fmt.Println("=========单行插入===========")
	fmt.Println("==========结构体============")
	t1 := Tag{Name: "t1"}
	result := db.Create(&t1)
	fmt.Println(result.Error)        // 返回 error
	fmt.Println(result.RowsAffected) // 返回插入记录的条数
	fmt.Println("==========MAP============")
	t2 := map[string]interface{}{"name": "t2"}
	result = db.Model(&Tag{}).Create(t2)
	fmt.Println(result.Error)        // 返回 error
	fmt.Println(result.RowsAffected) // 返回插入记录的条数

	fmt.Println("=========多行插入===========")
	fmt.Println("==========结构体============")
	t3s := []Tag{
		{Name: "t3"},
		{Name: "t4"},
		{Name: "t5"},
	}
	result = db.Create(&t3s)
	fmt.Println(result.Error)        // 返回 error
	fmt.Println(result.RowsAffected) // 返回插入记录的条数
	fmt.Println("==========MAP============")
	t4s := []map[string]interface{}{
		{"name": "t6"},
		{"name": "t7"},
		{"name": "t8"},
	}
	result = db.Model(&Tag{}).Create(&t4s)
	fmt.Println(result.Error)        // 返回 error
	fmt.Println(result.RowsAffected) // 返回插入记录的条数

	fmt.Println("=========多行插入InBatch===========")
	fmt.Println("==========结构体============")
	t5s := []Tag{
		{Name: "t9"},
		{Name: "t10"},
		{Name: "t11"},
	}
	result = db.CreateInBatches(&t5s, 2) // 一次2条数据进行插入
	fmt.Println(result.Error)            // 返回 error
	fmt.Println(result.RowsAffected)     // 返回插入记录的条数
	fmt.Println("==========MAP============")
	t6s := []map[string]interface{}{
		{"name": "t12"},
		{"name": "t13"},
		{"name": "t14"},
	}
	result = db.Model(&Tag{}).CreateInBatches(t6s, 3) // 一次三条数据插入
	fmt.Println(result.Error)                         // 返回 error
	fmt.Println(result.RowsAffected)                  // 返回插入记录的条数

}

⚠️ 注意三点:

  1. 使用 map 插入数据时, 需要指定 Model.

  2. 使用 map 插入数据时, 不会自动更新 created_at, updated_at 等子段. 原因是, gorm 只会记录 map 中的字段.

  3. 使用 map 插入数据时, association 不会被调用,且主键也不会自动填充.

修改 Update

package main

import (
	"fmt"
	"log"
	"os"
	"time"

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

type Tag struct {
	gorm.Model
	Name string `json:"name"`
}

/* 等价与
type Tag struct {
	ID        uint `gorm:"primarykey"`
	CreatedAt time.Time
	UpdatedAt time.Time
	DeletedAt DeletedAt `gorm:"index"`
	Name string `json:"name"`
}*/

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

type User struct {
	gorm.Model
	Name     string `json:"name"`
	Password string `json:"password"`
}

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

var db *gorm.DB
var err error

func init() {
	// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情

	newLogger := logger.New(
		log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注)
		logger.Config{
			SlowThreshold:             time.Second,  // 慢 SQL 阈值
			LogLevel:                  logger.Error, // 日志级别
			IgnoreRecordNotFoundError: true,         // 忽略ErrRecordNotFound(记录未找到)错误
			Colorful:                  true,         // 启用彩色打印
		},
	)

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

	err = db.AutoMigrate(Tag{}, User{})
	if err != nil {
		panic("AutoMigrate Failed")
	}

}

func main() {
	// 创建User
	//u1 := User{Name: "u1", Password: "passwd1"}
	//u2 := User{Name: "u2", Password: "passwd2"}
	//us := []User{u1, u2}
	//db.Create(&us)

	fmt.Println("=========Save===========")
	// Save 会保存所有的字段,即使字段是零值
	var t1 Tag
	db.First(&t1)
	fmt.Println(t1)

	t1.Name = "t1 modified"
	db.Save(&t1)
	fmt.Println(t1)

	fmt.Println("=========Update 更新单列===========")
	var t2 Tag
	db.First(&t2, 2) // id = 2
	fmt.Println(t2)

	db.Model(&Tag{}).Where("id = ?", 2).Update("name", "t2 modified")
	db.First(&t2, 2) // id = 2
	fmt.Println(t2)

	fmt.Println("=========Updates 更新多列===========")
	// Updates 方法支持 struct 和 map[string]interface{} 参数。
	// 当使用 struct 更新时,默认情况下,GORM 只会更新非零值的字段

	fmt.Println("=========结构体===========")
	var u1 User
	db.First(&u1)
	fmt.Println(u1)

	db.Model(&User{}).Where("id = ?", 1).Updates(User{Name: "u1 modified", Password: ""})
	db.First(&u1, 1)
	fmt.Println(u1)

	fmt.Println("=========MAP===========")
	var u2 User
	db.First(&u2, 2)
	fmt.Println(u2)

	db.Model(&User{}).Where("id = ?", 2).Updates(map[string]interface{}{"name": "u2 modified", "password": ""})
	db.First(&u2, 2)
	fmt.Println(u2)
}

⚠️ 注意四点 :

  1. 新建 User 表, 可以看到 Save 会保存所有值, 即使是 0 值.

  2. Update 同样会保存 0 值, 没有在结构体或 map 中的字段, 会被忽略.

  3. Updates, 在使用结构体更新时, 不会更新 0 值的字段, map 则会更新所有存在的字段.

如果想使用结构体更新 0 值字段, 使用指针类型或 sql.NullXXX, 如 sql.NullString, sql.NullInt64, 包装一个 string or int64 类型的值, 以及一个 bool 值.

使用 map 更新字段时, 需要指定 model.

  1. 指定更新的字段使用 Select, 忽略更新的字段使用 Omit.

查询 Select

package main

import (
	"errors"
	"fmt"
	"log"
	"os"
	"time"

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

type Tag struct {
	gorm.Model
	Name string `json:"name"`
}

/* 等价与
type Tag struct {
	ID        uint `gorm:"primarykey"`
	CreatedAt time.Time
	UpdatedAt time.Time
	DeletedAt DeletedAt `gorm:"index"`
	Name string `json:"name"`
}*/

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

type User struct {
	gorm.Model
	Name     string `json:"name"`
	Password string `json:"password"`
}

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

var db *gorm.DB
var err error

func init() {
	// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情

	newLogger := logger.New(
		log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注)
		logger.Config{
			SlowThreshold:             time.Second,  // 慢 SQL 阈值
			LogLevel:                  logger.Error, // 日志级别
			IgnoreRecordNotFoundError: true,         // 忽略ErrRecordNotFound(记录未找到)错误
			Colorful:                  true,         // 启用彩色打印
		},
	)

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

	err = db.AutoMigrate(Tag{}, User{})
	if err != nil {
		panic("AutoMigrate Failed")
	}

}

func main() {
	fmt.Println("=========查询单条数据===========")
	var u1 User
	var u2 User
	var u3 User
	// 获取第一条记录(主键升序)
	db.First(&u1)
	// SELECT * FROM users ORDER BY id LIMIT 1;
	fmt.Println(u1)

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

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

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

	// 检查 ErrRecordNotFound 错误
	if errors.Is(result.Error, gorm.ErrRecordNotFound) {
		fmt.Println("record not found")
	} else {
		fmt.Println("record found successfully")
	}

	fmt.Println("=========查询多条数据===========")
	// 查询所有数据
	var users []User
	result = db.Find(&users)
	fmt.Println(result.RowsAffected) // 返回找到的记录数
	fmt.Println(users)

	result = db.Where("id in ?", []int64{1, 2}).Find(&users)
	fmt.Println(result.RowsAffected) // 返回找到的记录数
	fmt.Println(users)

}

⚠️:

查询时, 使用结构体需要传入指针或指定模型 Model(). 使用 map 时, 需要指定 Table()

删 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";

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. GORM 推荐使用软删除, 如果模型中有 gorm.DeletedAt, 则自动进行软删除.

给模型添加软删除:

type User struct {
  ID      int
  Deleted gorm.DeletedAt
  Name    string
}
2. 查询软删除的数据 Unscoped()
db.Unscoped().Where("age = 20").Find(&users)
// SELECT * FROM users WHERE age = 20;
3. 不使用软删除 Unscoped()
db.Unscoped().Delete(&order)
// DELETE FROM orders WHERE id=10;