gorm框架之常用增删改查(CRUD)

发布于:2024-01-30 ⋅ 阅读:(88) ⋅ 点赞:(0)

最好的文档其实是官方的文档,大家可以参考这个文档链接,本文也只是个搬运工:

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")

使用钩子

用户可以自定义实现 BeforeSaveBeforeCreateAfterSaveAfterCreate 方法,然后在创建记录的时候通过设置 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 提供了 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)

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);

更新(update)

删除(delete)

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到