文章目录
- 一 CRUD 接口—gorm创建
-
- 1.1 创建表
- 1.2 创建记录
- 1.3 用指定字段创建记录
- 1.4 批量插入
- 1.5 创建钩子
- 1.6 根据 Map 创建
- 1.7 关联创建
- 1.8 默认值
- 二 CRUD 接口—gorm查询
-
- 2.1 检索单个对象
- 2.2 用主键检索
- 2.3 检索全部对象
- 2.4 条件
-
- 2.4.1 String 条件
- 2.4.2 Struct & Map 条件
- 2.4.3 指定结构体查询字段
- 2.4.4 内联条件
- 2.4.5 Not 条件
- 2.4.6 Or 条件
- 2.5 选择特定字段
- 2.6 Order
- 2.7 Limit & Offset
- 2.8 Group By & Having
- 2.9 Distinct
- 2.10 Joins
-
- 2.10.1 Joins 预加载
- 2.10.2 Joins a Derived Table
- 2.11 Scan
- 三 CRUD 接口—gorm更新
-
- 3.1 保存所有字段
- 3.2 更新单个列
- 3.3 更新多列
- 3.4 更新选定字段
- 3.5 更新 Hook
- 3.6 批量更新
-
- 3.6.1 阻止全局更新
- 3.6.2 更新的记录数
- 3.7 高级选项
-
- 3.7.1 使用 SQL 表达式更新
- 3.7.2 根据子查询进行更新
- 3.7.3 不使用 Hook 和时间追踪
- 3.7.4 返回修改行的数据
- 3.7.5 检查字段是否有变更?
- 3.7.6 在 Update 时修改值
- 四 CRUD 接口—gorm删除
-
- 4.1 删除一条记录
- 4.2 根据主键删除
- 4.3 Delete Hook
- 4.4 批量删除
-
- 4.4.1 阻止全局删除
- 4.4.2 返回删除行的数据
- 4.5 软删除
-
- 4.5.1 查找被软删除的记录
- 4.5.2 永久删除
- 4.5.4 Delete Flag
-
- Unix Second
- Use `1` / `0` AS Delete Flag
- Mixed Mode
- 五 CRUD 接口—gorm原生SQL和SQL生成器
-
- 5.1 原生 SQL
- 5.2 命名参数
- 5.3 DryRun 模式
- 5.4 ToSQL
- 5.5 `Row` & `Rows`
- 5.6 将 `` 扫描至 model
- 5.7 Connection
- 5.8 Advanced
-
- 5.8.1 子句(Clause)
- 5.8.2 子句构造器
- 5.8.3 子句选项
- 5.8.4 StatementModifier
一 CRUD 接口—gorm创建
1.1 创建表
package main
import (
"time"
"/driver/mysql"
"/gorm"
)
var db *gorm.DB
func init() {
dsn := "root:960690@tcp(127.0.0.1:3306)/golang_db?charset=utf8mb4&parseTime=True&loc=Local"
d, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
})
if err != nil {
panic("failed to connect database")
}
db = d
}
type User struct {
gorm.Model
Name string
Age int
Birthday time.Time
}
var user = User{
Name: "Jinzhu",
Age: 18,
Birthday: time.Now(),
}
func creatTable() {
db.AutoMigrate(&User{
})
}
func main() {
creatTable()
}
运行结果:
1.2 创建记录
result := db.Create(&user) // 通过数据的指针来创建
fmt.Printf(":%v", user.ID)
fmt.Printf("插入记录的条数:%v", result.RowsAffected)
运行结果:
user.ID:1插入记录的条数:1
mysql中查看:
1.3 用指定字段创建记录
-
创建记录并更新给出的字段。
user := User{ Name: "Jinzhu", Age: 18, Birthday: time.Now(), } db.Select("Name", "Age", "CreatedAt").Create(&user)
mysql中查看:
-
创建一个记录且一同忽略传递给略去的字段值。
db.Omit("Name", "Age", "CreatedAt").Create(&user)
mysql中查看:
1.4 批量插入
要有效地插入大量记录,请将一个 slice
传递给 Create
方法。 GORM 将生成单独一条SQL语句来插入所有数据,并回填主键的值,钩子方法也会被调用。
var users = []User{
{
Name: "jinzhu1"}, {
Name: "jinzhu2"}, {
Name: "jinzhu3"}}
db.Create(&users)
for _, user := range users {
user.ID // 1,2,3
}
使用 CreateInBatches
分批创建时,你可以指定每批的数量,例如:
var users = []User{
{
name: "jinzhu_1"}, ...., {
Name: "jinzhu_10000"}}
// 数量为 100
db.CreateInBatches(users, 100)
Upsert 和 Create With Associations 也支持批量插入
注意 使用
CreateBatchSize
选项初始化 GORM 时,所有的创建& 关联INSERT
都将遵循该选项
db, err := gorm.Open(sqlite.Open(""), &gorm.Config{
CreateBatchSize: 1000,
})
db := db.Session(&gorm.Session{
CreateBatchSize: 1000})
users = [5000]User{
{
Name: "jinzhu", Pets: []Pet{
pet1, pet2, pet3}}...}
db.Create(&users)
// INSERT INTO users xxx (5 batches)
// INSERT INTO pets xxx (15 batches)
1.5 创建钩子
GORM 允许用户定义的钩子有 BeforeSave
, BeforeCreate
, AfterSave
, AfterCreate
创建记录时将调用这些钩子方法,请参考 Hooks 中关于生命周期的详细信息
func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
u.UUID = uuid.New()
if u.Role == "admin" {
return errors.New("invalid role")
}
return
}
如果您想跳过 钩子
方法,您可以使用 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)
1.6 根据 Map 创建
GORM 支持根据 map[string]interface{}
和 []map[string]interface{}{}
创建记录,例如:
db.Model(&User{
}).Create(map[string]interface{
}{
"Name": "jinzhu", "Age": 18,
})
// batch insert from `[]map[string]interface{}{}`
db.Model(&User{
}).Create([]map[string]interface{
}{
{
"Name": "jinzhu_1", "Age": 18},
{
"Name": "jinzhu_2", "Age": 20},
})
注意: 根据 map 创建记录时,association 不会被调用,且主键也不会自动填充
1.7 关联创建
创建关联数据时,如果关联值是非零值,这些关联会被 upsert,且它们的 Hook
方法也会被调用
type CreditCard struct {
gorm.Model
Number string
UserID uint
}
type User struct {
gorm.Model
Name string
CreditCard CreditCard
}
db.Create(&User{
Name: "jinzhu",
CreditCard: CreditCard{
Number: "411111111111"}
})
// INSERT INTO `users` ...
// INSERT INTO `credit_cards` ...
您也可以通过 Select
、 Omit
跳过关联保存,例如:
db.Omit("CreditCard").Create(&user)
// 跳过所有关联
db.Omit(clause.Associations).Create(&user)
1.8 默认值
您可以通过标签 default
为字段定义默认值,如:
type User struct {
ID int64
Name string `gorm:"default:galeone"`
Age int64 `gorm:"default:18"`
}
插入记录到数据库时,默认值 会被用于 填充值为 零值 的字段
注意 对于声明了默认值的字段,像
0
、''
、false
等零值是不会保存到数据库。您需要使用指针类型或 Scanner/Valuer 来避免这个问题,例如:
type User struct {
gorm.Model
Name string
Age *int `gorm:"default:18"`
Active sql.NullBool `gorm:"default:true"`
}
注意 若要数据库有默认、虚拟/生成的值,你必须为字段设置
default
标签。若要在迁移时跳过默认值定义,你可以使用default:(-)
,例如:
type User struct {
ID string `gorm:"default:uuid_generate_v3()"` // db func
FirstName string
LastName string
Age uint8
FullName string `gorm:"->;type:GENERATED ALWAYS AS (concat(firstname,' ',lastname));default:(-);"`
}
使用虚拟/生成的值时,你可能需要禁用它的创建、更新权限
二 CRUD 接口—gorm查询
2.1 检索单个对象
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)
如果你想避免
ErrRecordNotFound
错误,你可以使用Find
,比如(1).Find(&user)
,Find
方法可以接受struct和slice的数据。
First
和 Last
会根据主键排序,分别查询第一条和最后一条记录。 只有在目标 struct 是指针或者通过 ()
指定 model 时,该方法才有效。 此外,如果相关 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 `()`
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 (., `Code`)
type Language struct {
Code string
Name string
}
db.First(&Language{
})
// SELECT * FROM `languages` ORDER BY `languages`.`code` LIMIT 1
2.2 用主键检索
如果主键是数字类型,您可以使用 内联条件 来检索对象。 传入字符串参数时,需要特别注意 SQL 注入问题,查看 安全 获取详情.
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);
如果主键是字符串(例如像 uuid),查询将被写成这样:
db.First(&user, "id = ?", "1b74413f-f3b8-409f-ac47-e8c062e3472a")
// SELECT * FROM users WHERE id = "1b74413f-f3b8-409f-ac47-e8c062e3472a";
When the destination object has a primary value, the primary key will be used to build the condition, for example:
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;
2.3 检索全部对象
// Get all records
result := db.Find(&users)
// SELECT * FROM users;
result.RowsAffected // returns found records count, equals `len(users)`
result.Error // returns error
2.4 条件
2.4.1 String 条件
// 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)