最好的文档其实是官方的文档,大家可以参考这个文档链接,本文也只是个搬运工:
GORM 指南 | GORM - The fantastic ORM library for Golang, aims to be developer friendly.
数据表初始化
为了边学边练习,我自己下载了一个 postgres 作为示例,然后使用 gorm 进行连接处理。为了简化,所有 go 代码都写在了一个文件,自测能成功运行。
main.go
package main
import (
"fmt"
"os"
"strings"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
// User model
type User struct {
Id int `gorm:"column:id;primarykey" json:"id"`
Name string `gorm:"column:name;index:,unique" json:"name"`
Age int8 `gorm:"column:age" json:"age"`
Mailbox string `gorm:"column:mailbox" json:"mailbox"`
Comment string `gorm:"column:comment" json:"comment"`
}
func (u *User) TableName() string {
return "users"
}
type Database struct {
Host string
DBName string
Username string
Password string
Port int
MaxIdleConns int
MaxOpenConns int
}
func getDatabaseConf() *Database {
return &Database{
Host: "127.0.0.1",
DBName: "postgres",
Username: "postgres",
Password: "123456",
Port: 5432,
MaxIdleConns: 5,
MaxOpenConns: 10,
}
}
func connect(conf *Database) (*gorm.DB, error) {
var (
err error
)
dsn := fmt.Sprintf("postgres://%s:%s@%s:%d/%s",
conf.Username, conf.Password, conf.Host, conf.Port, conf.DBName)
gormDB, err := gorm.Open(postgres.Open(dsn))
if err != nil {
return nil, err
}
sqlDB, err := gormDB.DB()
if err != nil {
return nil, err
}
if err = sqlDB.Ping(); err != nil {
return nil, err
}
sqlDB.SetMaxIdleConns(conf.MaxIdleConns)
sqlDB.SetMaxOpenConns(conf.MaxOpenConns)
return gormDB, nil
}
func initTable(db *gorm.DB, tables []interface{}) {
for _, table := range tables {
if err := db.AutoMigrate(table); err != nil {
if !strings.Contains(err.Error(), "already exists") {
panic(err)
}
}
}
}
func main() {
conf := getDatabaseConf()
connDb, err := connect(conf)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
var tables = []interface{}{
new(User),
}
initTable(connDb, tables)
}
go.mod
(NOTE:如果你 go 版本不是很高的话,你对应引用 gorm 相关版本最好也不要太高,不然可能导致 package 各种不兼容)
module github.com/test
go 1.17
require (
gorm.io/driver/postgres v1.4.5
gorm.io/gorm v1.24.1-0.20221019064659-5dd2bb482755
)
require (
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/pgconn v1.13.0 // indirect
github.com/jackc/pgio v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgproto3/v2 v2.3.1 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgtype v1.12.0 // indirect
github.com/jackc/pgx/v4 v4.17.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/stretchr/testify v1.8.1 // indirect
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/text v0.13.0 // indirect
)
新建(create)
新建单条记录
一般新建记录的话,我们主要关心的是 Error 信息,如果新建有报错信息的话,是需要我们返回错误进行处理。
func main() {
conf := getDatabaseConf()
db, err := connect(conf)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
// var tables = []interface{}{
// new(User),
// }
// initTable(connDb, tables)
user := User{Name: "Jinzhu", Age: 18, Mailbox: "test@qq.com", Comment: "test comment"}
result := db.Create(&user)
if result.Error != nil { // 返回 error
fmt.Println(result.Error)
return
}
fmt.Println(user.Id) // 2 返回插入数据得主键
fmt.Println(result.RowsAffected) // 1 返回插入记录的条数
}
postgres=# select * from users;
id | name | age | mailbox | comment
----+--------+-----+-------------+--------------
2 | Jinzhu | 18 | test@qq.com | test comment
(1 行记录)
新建多项记录
users := []*User{
{Name: "test1", Age: 18, Mailbox: "test@qq.com", Comment: "test comment"},
&User{Name: "test2", Age: 18, Mailbox: "test@qq.com", Comment: "test comment"},
}
result := db.Create(users)
postgres=# select * from users;
id | name | age | mailbox | comment
----+--------+-----+-------------+--------------
2 | Jinzhu | 18 | test@qq.com | test comment
3 | test1 | 18 | test@qq.com | test comment
4 | test2 | 18 | test@qq.com | test comment
(3 行记录)
NOTE 你无法向 ‘create’ 传递结构体,所以你应该传入数据的指针(也就是你数据表结构体实例对象取地址)。
用指定的字段创建记录
创建记录并为指定字段赋值。
user := User{Name: "test3", Age: 24, Mailbox: "test@qq.com", Comment: "test comment"}
result := db.Select("name", "mailbox").Create(&user)
// INSERT INTO `users` (`name`,`mailbox`) VALUES ("test3", "test@qq.com")
postgres=# select * from users order by id desc;
id | name | age | mailbox | comment
----+--------+-----+-------------+--------------
6 | test3 | | test@qq.com |
创建记录并忽略传递给 ‘Omit’ 的字段值。有点和上面指定字段赋值取反的意思。
user := User{Name: "test3", Age: 24, Mailbox: "test@qq.com", Comment: "test comment"}
result := db.Omit("Name", "Mailbox").Create(&user)
// INSERT INTO `users` (`age`,`comment`) VALUES (24, "test comment")
使用钩子
用户可以自定义实现 BeforeSave
, BeforeCreate
, AfterSave
, AfterCreate 方法,然后在创建记录的时候通过设置 SkipHooks 为 false 进行调用。
func main() {
conf := getDatabaseConf()
db, _ := connect(conf)
user := User{Name: "test5", Age: 24, Mailbox: "test@qq.com", Comment: "test comment"}
db.Session(&gorm.Session{SkipHooks: false}).Create(&user)
// db.Create(&user) 钩子默认启用,跳过的话设置参数 SkipHooks 为 true 即可。
}
func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
fmt.Println("before create")
return
}
func (u *User) AfterCreate(tx *gorm.DB) (err error) {
fmt.Println("after create")
return
}
func (u *User) BeforeSave(tx *gorm.DB) (err error) {
fmt.Println("before save")
return
}
func (u *User) AfterSave(tx *gorm.DB) (err error) {
fmt.Println("after save")
return
}
before save
before create
after create
after save
根据Map进行创建
func main() {
conf := getDatabaseConf()
db, _ := connect(conf)
user := map[string]interface{}{
"name": "test8",
"Age": 24,
}
db.Model(&User{}).Create(user)
}
func main() {
conf := getDatabaseConf()
db, _ := connect(conf)
users := []map[string]interface{}{
{"Name": "test1", "Age": 19},
{"Name": "test2", "Age": 20},
}
db.Model(&User{}).Create(&users)
}
默认值
一般来说,如果 Model 定义的时候没有特地指定默认值,新建记录的时候也没有给字段传递值,那么就会用对应字段的零值;如果定义 Model 的时候指定了默认值,新建的时候即使对应字段没有传值,也会使用 Model 中的默认值。如下面的 comment 和 mailbox 字段,新建用户时都未进行指定值,但创建的记录中 comment 使用了 model 中的默认值 hello world,而 mailbox 字段则直接使用了对应的零值空字符串。
// User model
type User struct {
Id int `gorm:"column:id;primarykey" json:"id"`
Name string `gorm:"column:name;index:,unique" json:"name"`
Age int8 `gorm:"column:age" json:"age"`
Mailbox string `gorm:"column:mailbox" json:"mailbox"`
Comment string `gorm:"column:comment;default:hello world" json:"comment"`
}
func main() {
conf := getDatabaseConf()
db, _ := connect(conf)
// user := User{Name: "test1", Age: 25, Mailbox: "test@qq.com", Comment: "test comment"}
user := User{Name: "test1", Age: 25}
db.Create(&user)
}
postgres=# select * from users order by id desc;
id | name | age | mailbox | comment
----+-------+-----+---------+-------------
39 | test1 | 25 | | hello world
Upsert 及冲突处理
Upsert 也即 Update、Insert 的合称。其实也是大家常用的,简单来说就是新建记录的时候,如果检测到对应列的数据已经存在的话(将冲突检测交给了组件),需要对原记录进行什么样的处理(比如替换全部,替换指定字段,什么都不替换,等等)。下面是一些常见示例。
使用新值替换指定字段
func main() {
conf := getDatabaseConf()
db, _ := connect(conf)
user := User{Name: "test1", Age: 25, Mailbox: "test@qq.com", Comment: "test comment"}
db.Create(&user)
user.Age = 26
user.Mailbox = "new@qq.com"
user.Comment = "new comment"
db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "name"}},
DoUpdates: clause.AssignmentColumns([]string{"age", "mailbox"}),
}).Create(&user)
}
postgres=# select * from users order by id desc;
id | name | age | mailbox | comment
----+-------+-----+------------+--------------
32 | test1 | 26 | new@qq.com | test comment
用自定义的值替换指定字段
func main() {
conf := getDatabaseConf()
db, _ := connect(conf)
user := User{Name: "test1", Age: 25, Mailbox: "test@qq.com", Comment: "test comment"}
db.Create(&user)
user.Age = 26
user.Mailbox = "new@qq.com"
user.Comment = "new comment"
db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "name"}},
DoUpdates: clause.Assignments(map[string]interface{}{"age": 100}),
}).Create(&user)
}
postgres=# select * from users order by id desc;
id | name | age | mailbox | comment
----+-------+-----+-------------+--------------
35 | test1 | 100 | test@qq.com | test comment
使用新值替换全部字段
func main() {
conf := getDatabaseConf()
db, _ := connect(conf)
user := User{Name: "test1", Age: 25, Mailbox: "test@qq.com", Comment: "test comment"}
db.Create(&user)
user.Age = 26
user.Mailbox = "new@qq.com"
user.Comment = "new comment"
db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "name"}},
UpdateAll: true,
}).Create(&user)
}
postgres=# select * from users order by id desc;
id | name | age | mailbox | comment
----+-------+-----+------------+-------------
33 | test1 | 26 | new@qq.com | new comment
保留旧值不进行任何替换
func main() {
conf := getDatabaseConf()
db, _ := connect(conf)
user := User{Name: "test1", Age: 25, Mailbox: "test@qq.com", Comment: "test comment"}
db.Create(&user)
user.Age = 26
user.Mailbox = "new@qq.com"
user.Comment = "new comment"
db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "name"}},
DoNothing: true,
}).Create(&user)
}
postgres=# select * from users order by id desc;
id | name | age | mailbox | comment
----+-------+-----+-------------+--------------
34 | test1 | 25 | test@qq.com | test comment
查询(read)
查询单条记录
GORM 提供了 First
、Take
、Last
方法,以便从数据库中检索单个对象。当查询数据库时它添加了 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)
First 和 Last 默认按照主键 id 排序后取值,如果相关 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
根据主键查询
主键是数字
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);
主键是字符串
db.First(&user, "id = ?", "1b74413f-f3b8-409f-ac47-e8c062e3472a")
// SELECT * FROM users WHERE id = "1b74413f-f3b8-409f-ac47-e8c062e3472a";
使用目标对象主键构建查询条件
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;
NOTE: 如果使用 gorm 的特定字段类型(例如 gorm.DeletedAt
),它将运行不同的查询来检索对象(我特地查了一下,DeletedAt 表示软删除。简单来说就是,执行 Delete 删除操作后,会给这个字段设置当前时间戳,表示这条数据在这个时刻已经被删除——尽管这条数据仍在数据库真实存在)。也就是说,查询的结果不会包含已经软删除的数据。
type User struct {
ID string `gorm:"primarykey;size:16"`
Name string `gorm:"size:24"`
DeletedAt gorm.DeletedAt `gorm:"index"`
}
var user = User{ID: 15}
db.First(&user)
// SELECT * FROM `users` WHERE `users`.`id` = '15' AND `users`.`deleted_at` IS NULL ORDER BY `users`.`id` LIMIT 1
查询全部记录
直接使用不带过滤条件的 Find 语句即可查询全部记录。
// Get all records
result := db.Find(&users)
// SELECT * FROM users;
result.RowsAffected // returns found records count, equals `len(users)`
result.Error // returns error
按照条件查询
Where 条件查询应该是我们查询时最常用的查询了,这个也是和原生的SQL最为接近的。
// 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';
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);