Gorm (ORM框架)
基本使用
示例代码:
package main
import (
"gorm.io/gorm"
"gorm.io/driver/sqlite"
)
type Product struct {
gorm.Model
Code string
Price uint
}
// 为model定义表名
func (p Product) TableName() string{
return "product"
}
func main() {
db, err := gorm.Open(
mysql.Open(dsn:"user:pass@top(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"),
&gorm.Config{})
if err != nil {
panic("failed to connect database")
}
... // 各种操作
}
约定:
- Gorm使用名为ID的字段作为主键
- 使用结构体的蛇形作为列名
- 使用CreatedAt, UpdatedAt字段作为创建、更新时间
以上代码中包括如下步骤:
- 定义gorm model,对应数据库中的一张表,字段对应表中字段,如果没有实现Tablename,就会使用蛇形负数
- 为model定义表名,
- 通过Open连接数据库Mysql,同时通过gorm.Config添加一些配置
- 使用Creat创建数据,创建单个数据时传递对象,创建多条数据时传递切片,
- 查询数据时先申明一个结构体,再通过First查询单条记录并返回到这个结构体里同时传递一个查询条件,1指根据整形逐渐查找
- 使用Update更新单条数据,使用Updates更新多条数据,使用map时可以更新零值
- 通过Delete删除,支持传入一个结构体以及条件
DSN
DSN 是数据库源名称(Data Source Name)的缩写,它是一个字符串,用于唯一标识数据库并提供连接细节。
DSN 通常包含以下信息:
- 数据库类型(MySQL,SQL Server,Oracle等)
- 服务器名称或 IP 地址
- 端口号
- 数据库名称
- 用户名和密码(可选)
例如,一个 MySQL DSN 可能如下所示:
mysql://username:password@localhost:3306/mydatabase
这个 DSN 指定了:
- 数据库类型:mysql
- 用户名:username
- 密码:password
- 服务器:localhost
- 端口:3306
- 数据库名称:mydatabase
创建数据
创建结构体
type Product struct {
ID uint `gorm:"primarykey"`
Code string `gorm:"column: code"`
Price uint `gorm:"column user_id"`
}
创建一条数据
p := &Product{Code: "D42"}
res := db.Creat(p)
fmt.Println(res.Error) // 获取error
fmt.Println(p.ID) // 返回插入数据的主键
因为gorm是链式调用,会返回一个gorm对象,可以用这个来获取error和主键
没有传主键时可以设置自动主键
创建多条数据
使用list创建
products := []*Product{{Code: "D41"}, {Code: "D42"}, {Code: "D43"}}
res := db.Creat(products)
fmt.Println(res.Error) // 获取err
for _, p := range products {
fmt.Println(p.ID)
}
Upsert中唯一索引冲突
使用clause.OnConfict处理数据冲突,以下以不处理冲突为例创建一条数据
p := &Product{Code: "D42", ID: 1}
db.Clauses(clause.OnConfict{DoNothing: true}).Creat(&p)
DoNothing: true即表示遇见冲突时不处理
主要参数包含:
- Columns: 定义唯一索引的列,用于唯一性检查。
- DoUpdates: 设置在冲突时要更新的字段,类似ON CONFLICT DO UPDATE。
- DoNothing: 如果为true,在冲突时啥也不做,忽略错误。
使用默认值
通过default标签为字段定义默认值 示例:
type User struct{
ID int64
Name string `gorm:"default:galeone"`
Age int64 `gorm:"default:18"`
}
注意:
- default只在创建时插入默认值,不会在更新时自动更新。
- 默认值不会用于读操作,只会返回实际保存的值。
- 默认值不会用于构造器和更新All。
查询数据
GORM 提供了 First、Take、Last 方法,以便从数据库中检索单个对象。当查询数据库时它添加了 LIMIT 1 条件,且没有找到记录时,它会返回 ErrRecordNotFound 错误
获得第一条数据
// (主键升序)
db.First(&user)
// SELECT * FROM users ORDER BY id LIMIT 1;
// 检查 ErrRecordNotFound 错误
errors.Is(result.Error, gorm.ErrRecordNotFound)
注意:First查询不到数据时会返回ErrRecordNotFound,可以代替使用Find,Find会返回空数组而不是错误
查询多条数据
users := make([]*User, 0)
result := db.Where(query:"age > 10").Find(&users) // SELECT * FROM users where age > 10;
fmt.Println(result.RowAffected)
fmt.Println(result.Error) // return error
通过where传入查询条件在调用Find查询,再通过result.RowAffected返回找到的记录数,相当于len(users)
string条件
// IN SELECT * FROM users WHERE name IN ('jinzhu','jinzhu 2');
db.Where(query:“name IN?", []string{"jinzhu", "jinzhu 2"})
// LIKE SELECT * FROM users WHERE name LIKE `%jin%`;
db.Where(query:"name = ?", args…:"%jin%").Find(&users)
// AND SELECT * FROM users WHERE name = 'jinzhu' AND age >= 22;
db.Where(query:"name = ? AND age >= ?", args…:"jinzhu", "22").Find(&users)
关于LIKE注意点:
- LIKE默认是区分大小写的,可以使用ILIKE实现大小写不敏感的模糊查询。
- LIKE通常需要结合占位符?使用,避免SQL注入。
- 可以使用gorm.Expr实现OR联合的LIKE查询。
- 上述LIKE代码中%表示被省略的内容
当使用结构体作为查询条件时,GORM会查询非零值字段。这意味着如果字段值为0、”、false或其他零值,该字段不会被用于构建查询条件,使用Map来构建查询条件
// SELECT * FROM users WHERE name = "jinzhu";
db.Where(&User{Name: "jinzhu", Age: 0}).Find(&users)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 0;
db.Where(map[string]interface{}{"Name": "jinzhu", "Age": 0}).Find(&users)
更新数据
条件更新单个列
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;
需要使用Model去设置表名
更新多个列
根据 struct 更新属性
db.Model(&User{ID: 111}).Updates(User{Name: "hello", Age: 18, Active: false})
// UPDATE users SET name='hello', age=18, updated_at = '2013-11-17 21:34:10' WHERE id = 111;
没有where查询条件时用主键ID的值进行更新
使用 map 更新属性
db.Model(&User{ID: 111}).Updates(map[string]interface{}{"name": "hello", "age": 18, "actived": false})
// UPDATE users SET name='hello', age=18, actived=false, updated_at='2013-11-17 21:34:10' WHERE id=111;
使用Struct更新时,只会更新非零值,如果需要更新零值可以使用Map更新或使用Select选择字段
更新选定字段
使用Select API选定name更新
// User's ID is `111`:
db.Model(&user).Select("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "actived": false})
// UPDATE users SET name='hello' WHERE id=111;
产出数据
物理删除(从数据库中真正删除)
直接使用Delet删除
db.Delete(&User{}, 10) //DELETE FROM users WHERE id = 10;
db.Delete(&User{}, "10") //DELETE FROM users WHERE id = 10;
传递字符串或者是整数都会将其识别成主键
通过列表
db.Delete(&User{}, []int{1,2,3}]) //DELETE FROM users WHERE id IN (1,2,3);
条件查询
db.Where("name LIKE ?", "%jinzhu%").Delete(User{}) //DELETE FROM users where name LIKE "%jinzhu%";
db.Delete(User{}, "email LIKE ?", "%jinzhu%") //DELETE FROM users where name LIKE "%jinzhu%";
软删除
GORM提供了gorm.DeletedAt用于帮助用户实现软删
结构体中额外定义 Deleted gorm.DeletedAt 。拥有软删能力的Model调用Delete时,记录不会被从数据库中真正删除。但GORM会将DeletedAt置为当前时间,并且你不能再通过正常的查询方法找到该记录。
使用Unscoped可以查询到被软删的数据 示例:
// 删除一条
u := User{ID: 111}
db.Delete(&u)
// 批量删除
db.Where("age = ?", 20).Delete($User{})
uers := make([]*User, 0)
//查询时不会忽略被删除的记录
db.Unscoped().Where("age = 20").Find(&users)
事务
-
开始事务
tx := db.Begin()在食物中使用一些db操作时不能使用
tx而是db,因为开始操作会固化一个链接 -
回滚事务
tx.Rollback() -
提交事务
tx.Commit() -
Transaction方法自动提交事务,避免用户漏写Commit, Rollback
db.Transaction(func(tx *gorm.DB) error { if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil { // 返回 err Rollback事务 return err } if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil { return err } // 返回 nil 提交事务 return nil
}) ```
Hook
GORM在提供了CURD的Hook能力。Hook是在创建、查询、更新、删除等操作之前、之后自动调用的函数。如果任何Hook返回错误,GORM将停止后续的操作并回滚事务
func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
u.UUID = uuid.New()
if !u.IsValid() {
err = errors.New("can't save invalid data")
}
return
}
func (u *User) AfterCreate(tx *gorm.DB) (err error) {
if u.ID == 1 {
tx.Model(u).Update("role", "admin")
}
return
}
注意 在 GORM 中保存、删除操作会默认运行在事务上, 因此在事务完成之前该事务中所作的更改是不可见的,如果钩子返回了任何错误,则修改将被回滚。
func (u *User) AfterCreate(tx *gorm.DB) (err error) {
if !u.IsValid() {
return errors.New("rollback invalid user")
}
return nil
}