1、傻瓜示例
GORM通过将数据库表中的数据映射到面向对象的模型中,简化了数据库操作,使得开发者可以很方便的使用代码来操作数据库,而无需编写SQL语句。
目前有个mysql表:miniprogram_orders,其存储了所有用户对应的订单,表结构如下:
建表sql语句:
sql
CREATE TABLE `miniprogram_orders` (
`id` bigint NOT NULL AUTO_INCREMENT,
`order_sn` varchar(255) NOT NULL DEFAULT '' COMMENT '订单号',
`uid` varchar(50) NOT NULL DEFAULT '' COMMENT '用户唯一标识',
`name` varchar(255) NOT NULL DEFAULT '' COMMENT '姓名',
`sex` varchar(5) NOT NULL DEFAULT '' COMMENT '性别',
`identify_card_number` varchar(20) NOT NULL DEFAULT '' COMMENT '身份证号',
`country` varchar(15) NOT NULL DEFAULT '0' COMMENT '国家',
`identify_card_type` varchar(20) NOT NULL DEFAULT '' COMMENT '证件类型',
`born_date` varchar(11) NOT NULL DEFAULT '' COMMENT '出生日期',
`nation` varchar(10) NOT NULL DEFAULT '' COMMENT '民族',
`phone_num` varchar(20) NOT NULL DEFAULT '' COMMENT '手机号',
`email` varchar(255) NOT NULL DEFAULT '' COMMENT '邮箱',
`address` varchar(255) NOT NULL DEFAULT '' COMMENT '现居地址',
`commodity_id` varchar(255) NOT NULL DEFAULT '' COMMENT '商品id',
`play_type` varchar(255) NOT NULL DEFAULT '' COMMENT '比赛类型:团体赛、个人赛',
`application_pay` float NOT NULL DEFAULT '0' COMMENT '报名费用',
`status` int NOT NULL DEFAULT '0' COMMENT '订单状态',
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`deleted_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `uid` (`uid`),
KEY `order_sn` (`order_sn`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
表中有一条记录如下:
sql
mysql> select * from miniprogram_orders \G;
*************************** 1. row ***************************
id: 1
order_sn: c9dbe95f51d1391622eebc08a7afa671
uid: liupeng20240908
name: 棉花糖
sex: 男
identify_card_number: 130627202410031234
country: 中国
identify_card_type: 身份证
born_date: 2024-10-03
nation: 汉
phone_num: 13988886666
email: mianhuatang@163.com
address: 北京天安门
commodity_id: cc800badc8b91d51a40c092feb31848f
play_type: 个人赛
application_pay: 99
status: 1
created_at: 2024-09-08 21:53:19
updated_at: 2024-09-17 18:12:05
deleted_at: NULL
1 row in set (0.00 sec)
ERROR:
No query specified
假如有一个需求,查找某个人所有订单,代码实现如下:
- 定义表对应结构体
Go
type MiniprogramOrder struct {
gorm.Model //gorm预定义结构体
Uid string `json:"uid"`
OrderSn string `json:"order_sn"`
Name string `json:"name"`
Sex string `json:"sex"`
IdentifyCardNumber string `json:"identify_card_number"`
Country string `json:"country"`
IdentifyCardType string `json:"identify_card_type"`
BornDate string `json:"born_date"`
Nation string `json:"nation"`
PhoneNum string `json:"phone_number"`
Email string `json:"email"`
Address string `json:"address"`
CommodityId string `json:"commodity_id"`
PlayType string `json:"play_type"`
ApplicationPay float32 `json:"application_pay"`
Status int `json:"status"`
}
gorm.Model为gorm预定义的结构体,其定义如下:
Go
type Model struct {
ID uint `gorm:"primarykey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt DeletedAt `gorm:"index"`
}
ID
:每个记录的唯一标识符(主键)。CreatedAt
:在创建记录时自动设置为当前时间。UpdatedAt
:每当记录更新时,自动更新为当前时间。DeletedAt
:用于软删除(将记录标记为已删除,而实际上并未从数据库中删除)。
上面为数据库表信息以及对应结构体定义,下面是代码实现部分:
创建数据库链接:
Go
func initMysql() *gorm.DB {
var err error
dsn := "user:password@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
client, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
fmt.Println("failed to connect database" + err.Error())
return nil
}
fmt.Println("db connect success!")
return client
}
查找数据库记录:
Go
// 根据uid查询订单
func Select(db *gorm.DB, uid string) []MiniprogramOrder {
orderList := []MiniprogramOrder{}
db.Where("uid = ?", uid).Find(&orderList)
return orderList
}
测试代码:
Go
func main() {
var json = jsoniter.ConfigCompatibleWithStandardLibrary
db := initMysql()
if db == nil {
return
}
orderList := Select(db, "liupeng20240908")
str, _ := json.MarshalToString(orderList)
fmt.Println(str)
}
代码打印:
Go
[
{
"ID": 1,
"CreatedAt": "2024-09-08T21:53:19+08:00",
"UpdatedAt": "2024-09-17T18:12:05+08:00",
"DeletedAt": null,
"uid": "liupeng20240908",
"order_sn": "c9dbe95f51d1391622eebc08a7afa671",
"name": "棉花糖",
"sex": "男",
"identify_card_number": "130627202410031234",
"country": "中国",
"identify_card_type": "身份证",
"born_date": "2024-10-03",
"nation": "汉",
"phone_number": "13988886666",
"email": "mianhuatang@163.com",
"address": "北京天安门",
"commodity_id": "cc800badc8b91d51a40c092feb31848f",
"play_type": "个人赛",
"application_pay": 99,
"status": 1
}
]
全部代码如下:
Go
package main
import (
"fmt"
jsoniter "github.com/json-iterator/go"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type MiniprogramOrder struct {
gorm.Model //gorm预定义结构体
Uid string `json:"uid"`
OrderSn string `json:"order_sn"`
Name string `json:"name"`
Sex string `json:"sex"`
IdentifyCardNumber string `json:"identify_card_number"`
Country string `json:"country"`
IdentifyCardType string `json:"identify_card_type"`
BornDate string `json:"born_date"`
Nation string `json:"nation"`
PhoneNum string `json:"phone_number"`
Email string `json:"email"`
Address string `json:"address"`
CommodityId string `json:"commodity_id"`
PlayType string `json:"play_type"`
ApplicationPay float32 `json:"application_pay"`
Status int `json:"status"`
}
func initMysql() *gorm.DB {
var err error
dsn := "user:password@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
client, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
fmt.Println("failed to connect database" + err.Error())
return nil
}
fmt.Println("db connect success!")
return client
}
// 根据uid查询订单
func Select(db *gorm.DB, uid string) []MiniprogramOrder {
orderList := []MiniprogramOrder{}
db.Where("uid = ?", uid).Find(&orderList)
return orderList
}
func main() {
var json = jsoniter.ConfigCompatibleWithStandardLibrary
db := initMysql()
if db == nil {
return
}
orderList := Select(db, "liupeng20240908")
str, _ := json.MarshalToString(orderList)
fmt.Println(str)
}
2、gorm约定
2.1 使用id作为主键
默认情况下,GORM 会使用 ID
作为表的主键。
你可以通过标签 primaryKey
将其它字段设为主键:
Go
// 将 `UUID` 设为主键
type Animal struct {
ID int64
UUID string `gorm:"primaryKey"`
Name string
Age int64
}
同时也可以使用复合主键:
Go
type Product struct {
ID string `gorm:"primaryKey"`
LanguageCode string `gorm:"primaryKey"`
Code string
Name string
}
默认情况下,整型 PrioritizedPrimaryField
启用了 AutoIncrement
,要禁用它,您需要为整型字段关闭 autoIncrement
:
Go
type Product struct {
CategoryID uint64 `gorm:"primaryKey;autoIncrement:false"`
TypeID uint64 `gorm:"primaryKey;autoIncrement:false"`
}
2.2 复数表名
GORM 使用结构体名的 蛇形命名
作为表名。对于结构体MiniprogramOrder,根据约定,其表名称为:miniprogram_orders,当然你也可以修改默认表名,只需要实现Tabler接口即可:
Go
type Tabler interface {
TableName() string
}
// TableName 会将 User 的表名重写为 `new_user`
func (User) TableName() string {
return "new_user"
}
2.3 指定表名
我们也可以通过代码指定要访问的表名:
Go
db.Table("deleted_users").Where("name = ?", "jinzhu").Delete(&User{})
// DELETE FROM deleted_users WHERE name = 'jinzhu';
2.4 列名
根据约定,数据表的列名使用的是 struct 字段名的 蛇形命名:
Go
type User struct {
ID uint // 列名是 `id`
Name string // 列名是 `name`
Birthday time.Time // 列名是 `birthday`
CreatedAt time.Time // 列名是 `created_at`
}
您可以使用 column 标签或 命名策略 来覆盖列名:
Go
type Animal struct {
AnimalID int64 `gorm:"column:beast_id"` // 将列名设为 `beast_id`
Birthday time.Time `gorm:"column:day_of_the_beast"` // 将列名设为 `day_of_the_beast`
Age int64 `gorm:"column:age_of_the_beast"` // 将列名设为 `age_of_the_beast`
}
2.5 时间戳追踪
对于有 CreatedAt
字段的模型,创建记录时,如果该字段值为零值,则将该字段的值设为当前时间。
你可以通过将 autoCreateTime
标签置为 false
来禁用时间戳追踪,例如:
Go
type User struct {
CreatedAt time.Time `gorm:"autoCreateTime:false"`
}
对于有 UpdatedAt
字段的模型,更新记录时,将该字段的值设为当前时间。创建记录时,如果该字段值为零值,则将该字段的值设为当前时间,
你可以通过将 autoUpdateTime
标签置为 false
来禁用时间戳追踪,例如:
Go
type User struct {
UpdatedAt time.Time `gorm:"autoUpdateTime:false"`
}
了解这些约定后,就能够直到第一章节中查询订单函数,没有指定表名的原因了。
3、数据库基本操作
3.1 写入数据
数据的写入,可以写入单条,也可以单次批量写入数据:
Go
func Insert(db *gorm.DB, orders []MiniprogramOrder) error {
resp := db.Create(orders)
if resp.Error != nil {
fmt.Println(resp)
return resp.Error
}
return nil
}
也可以通过db.CreateInBatches
方法来指定批量插入的批次大小:
Go
func Insert(db *gorm.DB, orders []MiniprogramOrder, size int) error {
if size > 0 {
resp := db.CreateInBatches(orders, size)
if resp.Error != nil {
fmt.Println(resp)
return resp.Error
}
} else {
resp := db.Create(orders)
if resp.Error != nil {
fmt.Println(resp)
return resp.Error
}
}
return nil
}
3.2 查询
3.2.1 查询单个对象
Go
// 获取第一条记录(主键升序)
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;
3.2.2 根据主键检索
Go
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);
3.2.3 条件检索
Go
// Get first matched record
db.Where("name = ?", "jinzhu").First(&user)
// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;
// 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';
3.2.4 not条件查询
Go
db.Not("name = ?", "jinzhu").First(&user)
// SELECT * FROM users WHERE NOT name = "jinzhu" ORDER BY id LIMIT 1;
// Not In
db.Not(map[string]interface{}{"name": []string{"jinzhu", "jinzhu 2"}}).Find(&users)
// SELECT * FROM users WHERE name NOT IN ("jinzhu", "jinzhu 2");
// Struct
db.Not(User{Name: "jinzhu", Age: 18}).First(&user)
// SELECT * FROM users WHERE name <> "jinzhu" AND age <> 18 ORDER BY id LIMIT 1;
// Not In slice of primary keys
db.Not([]int64{1,2,3}).First(&user)
// SELECT * FROM users WHERE id NOT IN (1,2,3) ORDER BY id LIMIT 1;
3.2.5 or条件查询
Go
db.Where("role = ?", "admin").Or("role = ?", "super_admin").Find(&users)
// SELECT * FROM users WHERE role = 'admin' OR role = 'super_admin';
// Struct
db.Where("name = 'jinzhu'").Or(User{Name: "jinzhu 2", Age: 18}).Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' OR (name = 'jinzhu 2' AND age = 18);
// Map
db.Where("name = 'jinzhu'").Or(map[string]interface{}{"name": "jinzhu 2", "age": 18}).Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' OR (name = 'jinzhu 2' AND age = 18);
3.2.6 select特定字段查询
Go
db.Select("name", "age").Find(&users)
// SELECT name, age FROM users;
db.Select([]string{"name", "age"}).Find(&users)
// SELECT name, age FROM users;
db.Table("users").Select("COALESCE(age,?)", 42).Rows()
// SELECT COALESCE(age,'42') FROM users;
3.2.7 排序
Go
db.Order("age desc, name").Find(&users)
// SELECT * FROM users ORDER BY age desc, name;
// Multiple orders
db.Order("age desc").Order("name").Find(&users)
// SELECT * FROM users ORDER BY age desc, name;
db.Clauses(clause.OrderBy{
Expression: clause.Expr{SQL: "FIELD(id,?)", Vars: []interface{}{[]int{1, 2, 3}}, WithoutParentheses: true},
}).Find(&User{})
// SELECT * FROM users ORDER BY FIELD(id,1,2,3)
3.2.8 limit查询
Go
db.Limit(3).Find(&users)
// SELECT * FROM users LIMIT 3;
// Cancel limit condition with -1
db.Limit(10).Find(&users1).Limit(-1).Find(&users2)
// SELECT * FROM users LIMIT 10; (users1)
// SELECT * FROM users; (users2)
db.Offset(3).Find(&users)
// SELECT * FROM users OFFSET 3;
db.Limit(10).Offset(5).Find(&users)
// SELECT * FROM users OFFSET 5 LIMIT 10;
// Cancel offset condition with -1
db.Offset(10).Find(&users1).Offset(-1).Find(&users2)
// SELECT * FROM users OFFSET 10; (users1)
// SELECT * FROM users; (users2)
3.2.9 group by
Go
type result struct {
Date time.Time
Total int
}
db.Model(&User{}).Select("name, sum(age) as total").Where("name LIKE ?", "group%").Group("name").First(&result)
// SELECT name, sum(age) as total FROM `users` WHERE name LIKE "group%" GROUP BY `name` LIMIT 1
db.Model(&User{}).Select("name, sum(age) as total").Group("name").Having("name = ?", "group").Find(&result)
// SELECT name, sum(age) as total FROM `users` GROUP BY `name` HAVING name = "group"
rows, err := db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Rows()
defer rows.Close()
for rows.Next() {
...
}
rows, err := db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Having("sum(amount) > ?", 100).Rows()
defer rows.Close()
for rows.Next() {
...
}
type Result struct {
Date time.Time
Total int64
}
db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Having("sum(amount) > ?", 100).Scan(&results)
3.3 更新
3.3.1 更新单个列
Go
// 根据条件更新
db.Model(&User{}).Where("active = ?", true).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE active=true;
// User 的 ID 是 `111`
db.Model(&user).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;
// 根据条件和 model 的值进行更新
db.Model(&user).Where("active = ?", true).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111 AND active=true;
3.3.2 更新选定列
如果您想要在更新时选择、忽略某些字段,您可以使用 Select
、Omit
Go
/ 选择 Map 的字段
// User 的 ID 是 `111`:
db.Model(&user).Select("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET name='hello' WHERE id=111;
db.Model(&user).Omit("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;
// 选择 Struct 的字段(会选中零值的字段)
db.Model(&user).Select("Name", "Age").Updates(User{Name: "new_name", Age: 0})
// UPDATE users SET name='new_name', age=0 WHERE id=111;
// 选择所有字段(选择包括零值字段的所有字段)
db.Model(&user).Select("*").Updates(User{Name: "jinzhu", Role: "admin", Age: 0})
// 选择除 Role 外的所有字段(包括零值字段的所有字段)
db.Model(&user).Select("*").Omit("Role").Updates(User{Name: "jinzhu", Role: "admin", Age: 0})
3.3.4 批量更新
如果没有通过 Model
指定一个含有主键的记录,GORM 会执行批量更新
Go
// Update with struct
db.Model(User{}).Where("role = ?", "admin").Updates(User{Name: "hello", Age: 18})
// UPDATE users SET name='hello', age=18 WHERE role = 'admin';
// Update with map
db.Table("users").Where("id IN ?", []int{10, 11}).Updates(map[string]interface{}{"name": "hello", "age": 18})
// UPDATE users SET name='hello', age=18 WHERE id IN (10, 11);
3.4 删除
3.4.1 删除单条记录
删除一条记录时,删除对象需要指定主键,否则会触发批量删除,例如:
// 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";
3.4.2 批量删除
db.Where("email LIKE ?", "%jinzhu%").Delete(&Email{})
// DELETE from emails where email LIKE "%jinzhu%";
db.Delete(&Email{}, "email LIKE ?", "%jinzhu%")
// DELETE from emails where email LIKE "%jinzhu%";
3.4.3 逻辑删除
如果你的模型包含了 gorm.DeletedAt
字段(该字段也被包含在gorm.Model
中),那么该模型将会自动获得软删除的能力!
当调用Delete
时,GORM并不会从数据库中删除该记录,而是将该记录的DeleteAt
设置为当前时间,而后的一般查询方法将无法查找到此条记录。
Go
// user's ID is `111`
db.Delete(&user)
// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE id = 111;
// Batch Delete
db.Where("age = ?", 20).Delete(&User{})
// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE age = 20;
// Soft deleted records will be ignored when querying
db.Where("age = 20").Find(&user)
// SELECT * FROM users WHERE age = 20 AND deleted_at IS NULL;
被逻辑删除的数据,也能通过api可以查找出来:
Go
db.Unscoped().Where("age = 20").Find(&users)
// SELECT * FROM users WHERE age = 20;
4、几个钩子函数
Hook 是在创建、查询、更新、删除等操作之前、之后调用的函数。
4.1 创建hook
GORM允许用户通过实现这些接口 BeforeSave
, BeforeCreate
, AfterSave
, AfterCreate
来自定义钩子。 这些钩子方法会在创建一条记录时被调用:
Go
func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
u.UUID = uuid.New()
if u.Role == "admin" {
return errors.New("invalid role")
}
return
}
4.2 更新hook
GORM 支持的 hook 包括:BeforeSave
, BeforeUpdate
, AfterSave
, AfterUpdate
. 更新记录时将调用这些方法:
Go
func (u *User) BeforeUpdate(tx *gorm.DB) (err error) {
if u.Role == "admin" {
return errors.New("admin user not allowed to update")
}
return
}
4.3 删除hook
对于删除操作,GORM 支持 BeforeDelete
、AfterDelete
Hook,在删除记录时会调用这些方法:
Go
func (u *User) BeforeDelete(tx *gorm.DB) (err error) {
if u.Role == "admin" {
return errors.New("admin user not allowed to delete")
}
return
}
5、事务
Gorm 支持直接调用事务控制方法(commit、rollback),例如:
Go
// 开始事务
tx := db.Begin()
// 在事务中执行一些 db 操作(从这里开始,您应该使用 'tx' 而不是 'db')
tx.Create(...)
// ...
// 遇到错误时回滚事务
tx.Rollback()
// 否则,提交事务
tx.Commit()
示例:
Go
func CreateAnimals(db *gorm.DB) error {
// 再唠叨一下,事务一旦开始,你就应该使用 tx 处理数据
tx := db.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
if err := tx.Error; err != nil {
return err
}
if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
tx.Rollback()
return err
}
if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {
tx.Rollback()
return err
}
return tx.Commit().Error
}
6、总结
总之,gorm是一个对go开发者非常友好且使用简单的db操作库,非常容易上手,学习成本很低,推荐使用。
上面对操作数据库的基本操作已经介绍的七七八八了,完全可以满足日常开发,总结出来共享给更多的小伙伴,希望对大家有所帮助。