Gorm

1. 入门

1.1 配置

下载mysql的驱动

go get gorm.io/driver/mysql
go get gorm.io/gorm

连接:

username := "root"  //账号
password := "123456" //密码
host := "127.0.0.1" //数据库地址,可以是Ip或者域名
port := 3306 //数据库端口
Dbname := "gorm" //数据库名
timeout := "10s" //连接超时,10秒

// root:root@tcp(127.0.0.1:3306)/gorm?
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local&timeout=%s", username, password, host, port, Dbname, timeout)
//连接MYSQL, 获得DB类型实例,用于后面的数据库读写操作。
db, err := gorm.Open(mysql.Open(dsn), gorn.Config{})
if err != nil {
panic("连接数据库失败, error=" + err.Error())
}
// 连接成功
fmt.Println(db)

1.2 操作

引入gorm包

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

定义表结构体

type Product struct {
gorm.Model
Code string
Price uint
}

打开数据库

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

数据库迁移

// 迁移 schema
db.AutoMigrate(&Product{})

建立字段数据

// Create
db.Create(&Product{Code: "D42", Price: 100})

查找记录

// Read
var product Product
db.First(&product, 1) // 根据整型主键查找
db.First(&product, "code = ?", "D42") // 查找 code 字段值为 D42 的记录

更新字段数据。查找和更新要一起执行,db.First意思是首先查找按主键排序的第一条记录,匹配给定条件。如果再次运行go文件,那么会开始第二条更新。

// Update - 将 product 的 price 更新为 200
db.Model(&product).Update("Price", 200)
// Update - 更新多个字段
db.Model(&product).Updates(Product{Price: 200, Code: "F42"}) // 仅更新非零值字段
db.Model(&product).Updates(map[string]interface{}{"Price": 200, "Code": "F42"})

删除字段,同上。注意,这里是软删除,即实际上并不会删除数据库中的内容,而是在delete_at中记录一个删除时间,表示已删除,应用程序可以在需要时忽略它们。

// Delete - 删除 product
db.Delete(&product, 1)

1.3 模型定义

GORM 通过将 Go 结构体(Go structs) 映射到数据库表来简化数据库交互。

模型是使用普通结构体定义的。

约定

  1. 主键:GORM 使用一个名为ID 的字段作为每个模型的默认主键。
  2. 表名:默认情况下,GORM 将结构体名称转换为 snake_case 并为表名加上复数形式。 例如,一个 User 结构体在数据库中的表名变为 users
  3. 列名:GORM 自动将结构体字段名称转换为 snake_case 作为数据库中的列名。
  4. 时间戳字段:GORM使用字段 CreatedAtUpdatedAt 来自动跟踪记录的创建和更新时间。

gorm.Model

GORM提供了一个预定义的结构体,名为gorm.Model

// gorm.Model 的定义
type Model struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time //创建时间
UpdatedAt time.Time //更新时间
DeletedAt gorm.DeletedAt `gorm:"index"` //删除时间,用于软删除(将记录标记为已删除,而实际上并未从数据库中删除)。
}

高级选项:

字段级权限控制

可导出的字段在使用 GORM 进行 CRUD 时拥有全部的权限,此外,GORM 允许您用标签控制字段级别的权限。这样您就可以让一个字段的权限是只读、只写、只创建、只更新或者被忽略

type User struct {
Name string `gorm:"<-:create"` // 允许读和创建
Name string `gorm:"<-:update"` // 允许读和更新
Name string `gorm:"<-"` // 允许读和写(创建和更新)
Name string `gorm:"<-:false"` // 允许读,禁止写
Name string `gorm:"->"` // 只读(除非有自定义配置,否则禁止写)
Name string `gorm:"->;<-:create"` // 允许读和写
Name string `gorm:"->:false;<-:create"` // 仅创建(禁止从 db 读)
Name string `gorm:"-"` // 通过 struct 读写会忽略该字段
Name string `gorm:"-:all"` // 通过 struct 读写、迁移会忽略该字段
Name string `gorm:"-:migration"` // 通过 struct 迁移会忽略该字段
}

嵌入结构体

对于匿名字段,GORM 会将其字段包含在父结构体中,

type User struct {
gorm.Model
Name string
}
// 等效于
type User struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
Name string
}

对于正常的结构体字段,你也可以通过标签 embedded 将其嵌入,

type Author struct {
Name string
Email string
}

type Blog struct {
ID int
Author Author `gorm:"embedded"`
Upvotes int32
}
// 等效于
type Blog struct {
ID int64
Name string
Email string
Upvotes int32
}

1.4 连接到数据库

MySQL

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

func main() {
// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情
dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
}

高级配置

db, err := gorm.Open(mysql.New(mysql.Config{
DSN: "gorm:gorm@tcp(127.0.0.1:3306)/gorm?charset=utf8&parseTime=True&loc=Local", // DSN data source name
DefaultStringSize: 256, // string 类型字段的默认长度
DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持
DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引
DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列
SkipInitializeWithVersion: false, // 根据当前 MySQL 版本自动配置
}), &gorm.Config{})

PostgreSQL

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

dsn := "host=localhost user=gorm password=gorm dbname=gorm port=9920 sslmode=disable TimeZone=Asia/Shanghai"
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})

连接池

sqlDB, err := db.DB()

// SetMaxIdleConns sets the maximum number of connections in the idle connection pool.
sqlDB.SetMaxIdleConns(10)

// SetMaxOpenConns sets the maximum number of open connections to the database.
sqlDB.SetMaxOpenConns(100)

// SetConnMaxLifetime sets the maximum amount of time a connection may be reused.
sqlDB.SetConnMaxLifetime(time.Hour)

2. CRUD接口

1.1 创建

创建记录

//	创建单个记录
user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}

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

user.ID // 返回插入数据的主键
result.Error // 返回 error
result.RowsAffected // 返回插入记录的条数

// 创建多项记录
users := []*User{
{Name: "Jinzhu", Age: 18, Birthday: time.Now()},
{Name: "Jackson", Age: 19, Birthday: time.Now()},
}

用指定的字段创建记录,即Sql中的插入数据

db.Select("Name", "Age", "CreatedAt").Create(&user)
// INSERT INTO `users` (`name`,`age`,`created_at`) VALUES ("jinzhu", 18, "2020-07-04 11:05:21.775")

db.Omit("Name", "Age", "CreatedAt").Create(&user)
// INSERT INTO `users` (`birthday`,`updated_at`) VALUES ("2020-01-01 00:00:00.000", "2020-07-04 11:05:21.775")

批量插入

插入大量记录,请将切片传递给Create方法。

GORM 将生成一条 SQL 来插入所有数据,以返回所有主键值,并触发 Hook 方法。

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

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

通过db.CreateInBatches方法来指定批量插入的批次大小

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

// batch size 100
db.CreateInBatches(users, 100)

CreateBatchSize用来初始化批量插入的批次大小

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

1.2 查询

检索单个对象

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)

钩子

钩子(Hooks)是GORM在执行某些数据库操作时自动调用的一系列方法。

  1. BeforeSave:在创建或更新记录之前调用。可以用于数据验证或设置某些字段的默认值。
  2. AfterSave:在创建或更新记录之后调用。可以用于执行一些后置操作,如发送通知、记录日志等。
  3. BeforeCreate:在创建记录之前调用。这个钩子通常用于在数据被保存到数据库之前设置时间戳或自动递增的ID等。
  4. AfterCreate:在创建记录之后调用。这个钩子可以用于在记录被成功创建后执行一些操作。
  5. BeforeUpdate:在更新记录之前调用。可以用于在数据被更新前进行验证或更新某些字段。
  6. AfterUpdate:在更新记录之后调用。可以用于执行一些更新后的操作。
  7. BeforeDelete:在删除记录之前调用。这个钩子通常用于防止某些关键数据的删除,或者记录删除操作的信息。
  8. AfterDelete:在删除记录之后调用。这个钩子可以用于清理与已删除记录相关联的数据或资源。

跳过Hooks方法,可以使用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)