Appearance
02-CRUD 操作与查询基础
CRUD(Create、Read、Update、Delete)是任何 ORM 的核心能力。GORM 提供了链式 API 让数据库操作变得直观且类型安全。本篇将系统讲解 GORM 的增删改查操作、各种查询条件、聚合函数、软删除等核心知识,并通过完整的用户管理示例巩固所学。
创建记录
Create 单条记录
使用 Create 方法插入一条记录,GORM 会自动填充 ID、CreatedAt 等字段。
go
package main
import (
"fmt"
"log"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type User struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"type:varchar(50)"`
Email string `gorm:"type:varchar(150);uniqueIndex"`
Age int
}
func main() {
db, err := gorm.Open(sqlite.Open("crud.db"), &gorm.Config{})
if err != nil {
log.Fatal(err)
}
db.AutoMigrate(&User{})
// 创建单条记录
user := User{Name: "张三", Email: "zhangsan@example.com", Age: 25}
result := db.Create(&user)
fmt.Printf("影响行数: %d\n", result.RowsAffected)
fmt.Printf("自增 ID: %d\n", user.ID) // Create 后 ID 会被自动填充
fmt.Printf("错误: %v\n", result.Error)
}批量创建
Create 也支持传入切片进行批量插入:
go
package main
import (
"fmt"
"log"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type User struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"type:varchar(50)"`
Email string `gorm:"type:varchar(150);uniqueIndex"`
Age int
}
func main() {
db, err := gorm.Open(sqlite.Open("crud.db"), &gorm.Config{})
if err != nil {
log.Fatal(err)
}
db.AutoMigrate(&User{})
users := []User{
{Name: "李四", Email: "lisi@example.com", Age: 30},
{Name: "王五", Email: "wangwu@example.com", Age: 28},
{Name: "赵六", Email: "zhaoliu@example.com", Age: 35},
}
result := db.Create(&users)
fmt.Printf("批量插入 %d 条记录\n", result.RowsAffected)
// 查询所有 ID
for _, u := range users {
fmt.Printf("ID: %d, Name: %s\n", u.ID, u.Name)
}
}指定字段创建
如果只想插入部分字段,可以使用 Select 指定:
go
package main
import (
"fmt"
"log"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type User struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"type:varchar(50)"`
Email string `gorm:"type:varchar(150);uniqueIndex"`
Age int
Status int `gorm:"default:1"`
}
func main() {
db, err := gorm.Open(sqlite.Open("crud.db"), &gorm.Config{})
if err != nil {
log.Fatal(err)
}
db.AutoMigrate(&User{})
// 只插入 Name 和 Email 字段,其他字段使用默认值
user := User{Name: "Tom", Email: "tom@example.com", Age: 20, Status: 0}
db.Select("Name", "Email").Create(&user)
// 查询验证
var found User
db.First(&found, user.ID)
fmt.Printf("Age(默认0): %d, Status(默认1): %d\n", found.Age, found.Status)
}读取记录
First、Take、Last
First:按主键升序取第一条Take:不排序取第一条Last:按主键降序取第一条
go
package main
import (
"fmt"
"log"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type User struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"type:varchar(50)"`
Email string `gorm:"type:varchar(150);uniqueIndex"`
Age int
}
func main() {
db, err := gorm.Open(sqlite.Open("crud.db"), &gorm.Config{})
if err != nil {
log.Fatal(err)
}
db.AutoMigrate(&User{})
// 准备数据
db.Create(&User{Name: "A", Email: "a@x.com", Age: 20})
db.Create(&User{Name: "B", Email: "b@x.com", Age: 25})
db.Create(&User{Name: "C", Email: "c@x.com", Age: 30})
var firstUser, takeUser, lastUser User
db.First(&firstUser) // SELECT * FROM users ORDER BY id LIMIT 1
db.Take(&takeUser) // SELECT * FROM users LIMIT 1
db.Last(&lastUser) // SELECT * FROM users ORDER BY id DESC LIMIT 1
fmt.Printf("First: %s\n", firstUser.Name)
fmt.Printf("Take: %s\n", takeUser.Name)
fmt.Printf("Last: %s\n", lastUser.Name)
// 按主键查询
var byID User
db.First(&byID, 2) // SELECT * FROM users WHERE id = 2
fmt.Printf("ID=2: %s\n", byID.Name)
// 按主键切片查询
var users []User
db.Find(&users, []int{1, 2, 3})
fmt.Printf("按 ID 切片查询到 %d 条\n", len(users))
}重要提示:First、Take、Last 找不到记录时会返回 ErrRecordNotFound 错误;而 Find 不会。
Find 查询多条
go
package main
import (
"fmt"
"log"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type User struct {
ID uint `gorm:"primaryKey"`
Name string
Email string
Age int
}
func main() {
db, err := gorm.Open(sqlite.Open("crud.db"), &gorm.Config{})
if err != nil {
log.Fatal(err)
}
db.AutoMigrate(&User{})
var users []User
result := db.Find(&users) // SELECT * FROM users
fmt.Printf("查询到 %d 条记录\n", result.RowsAffected)
// Find 不会返回 ErrRecordNotFound
emptyResult := db.Where("1 = 0").Find(&users)
fmt.Printf("空查询错误: %v, 行数: %d\n", emptyResult.Error, emptyResult.RowsAffected)
}查询条件
字符串条件
最灵活的查询方式,但要注意 SQL 注入风险(使用 ? 占位符避免):
go
package main
import (
"fmt"
"log"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type User struct {
ID uint `gorm:"primaryKey"`
Name string
Email string
Age int
}
func main() {
db, err := gorm.Open(sqlite.Open("crud.db"), &gorm.Config{})
if err != nil {
log.Fatal(err)
}
db.AutoMigrate(&User{})
db.Create(&User{Name: "Tom", Email: "tom@x.com", Age: 25})
var users []User
// 等值条件
db.Where("name = ?", "Tom").Find(&users)
fmt.Printf("name=Tom: %d 条\n", len(users))
// 比较条件
db.Where("age > ?", 20).Find(&users)
fmt.Printf("age>20: %d 条\n", len(users))
// IN 条件
db.Where("name IN ?", []string{"Tom", "Jerry"}).Find(&users)
fmt.Printf("name IN (Tom, Jerry): %d 条\n", len(users))
// LIKE 模糊查询
db.Where("name LIKE ?", "%o%").Find(&users)
fmt.Printf("name LIKE %%o%%: %d 条\n", len(users))
// BETWEEN
db.Where("age BETWEEN ? AND ?", 20, 30).Find(&users)
fmt.Printf("age BETWEEN 20 AND 30: %d 条\n", len(users))
// AND / OR 组合
db.Where("name = ? AND age > ?", "Tom", 20).Find(&users)
fmt.Printf("name=Tom AND age>20: %d 条\n", len(users))
}结构体条件
使用结构体作为条件,零值字段会被忽略:
go
package main
import (
"fmt"
"log"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type User struct {
ID uint `gorm:"primaryKey"`
Name string
Email string
Age int
}
func main() {
db, err := gorm.Open(sqlite.Open("crud.db"), &gorm.Config{})
if err != nil {
log.Fatal(err)
}
db.AutoMigrate(&User{})
db.Create(&User{Name: "Tom", Email: "tom@x.com", Age: 25})
db.Create(&User{Name: "Tom", Email: "tom2@x.com", Age: 0})
var users []User
// 结构体条件:零值字段被忽略,Age=0 不会作为条件
db.Where(&User{Name: "Tom", Age: 0}).Find(&users)
fmt.Printf("结构体条件(Age=0被忽略): %d 条\n", len(users))
// 这里会查询到所有 Name=Tom 的记录,包括 Age=0 的
}Map 条件
Map 条件会包含零值字段,适合需要精确匹配零值的场景:
go
package main
import (
"fmt"
"log"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type User struct {
ID uint `gorm:"primaryKey"`
Name string
Email string
Age int
}
func main() {
db, err := gorm.Open(sqlite.Open("crud.db"), &gorm.Config{})
if err != nil {
log.Fatal(err)
}
db.AutoMigrate(&User{})
db.Create(&User{Name: "Tom", Email: "tom@x.com", Age: 25})
db.Create(&User{Name: "Tom", Email: "tom2@x.com", Age: 0})
var users []User
// Map 条件:Age=0 会作为查询条件
db.Where(map[string]interface{}{"name": "Tom", "age": 0}).Find(&users)
fmt.Printf("Map 条件(Age=0作为条件): %d 条\n", len(users))
// 这里只查询到 Name=Tom AND Age=0 的记录
}选择字段 Select
Select 指定查询哪些字段,减少数据传输量:
go
package main
import (
"fmt"
"log"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type User struct {
ID uint `gorm:"primaryKey"`
Name string
Email string
Age int
}
func main() {
db, err := gorm.Open(sqlite.Open("crud.db"), &gorm.Config{})
if err != nil {
log.Fatal(err)
}
db.AutoMigrate(&User{})
db.Create(&User{Name: "Tom", Email: "tom@x.com", Age: 25})
// 只查询 name 和 age
var users []User
db.Select("name", "age").Find(&users)
for _, u := range users {
fmt.Printf("Name: %s, Age: %d, Email: %s\n", u.Name, u.Age, u.Email)
// Email 为空,因为没有查询
}
// 排除字段(使用 Omit)
db.Omit("email", "age").Find(&users)
for _, u := range users {
fmt.Printf("Name: %s\n", u.Name)
}
}排序 Order
go
package main
import (
"fmt"
"log"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type User struct {
ID uint `gorm:"primaryKey"`
Name string
Age int
Score float64
}
func main() {
db, err := gorm.Open(sqlite.Open("crud.db"), &gorm.Config{})
if err != nil {
log.Fatal(err)
}
db.AutoMigrate(&User{})
db.Create(&User{Name: "A", Age: 20, Score: 90.5})
db.Create(&User{Name: "B", Age: 25, Score: 85.0})
db.Create(&User{Name: "C", Age: 30, Score: 95.0})
var users []User
// 按 age 降序
db.Order("age desc").Find(&users)
fmt.Println("按 age 降序:")
for _, u := range users {
fmt.Printf(" %s: %d\n", u.Name, u.Age)
}
// 多字段排序
db.Order("score desc").Order("age asc").Find(&users)
fmt.Println("按 score 降序、age 升序:")
for _, u := range users {
fmt.Printf(" %s: score=%.1f age=%d\n", u.Name, u.Score, u.Age)
}
}分页 Limit + Offset
go
package main
import (
"fmt"
"log"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type User struct {
ID uint `gorm:"primaryKey"`
Name string
Age int
}
func page(db *gorm.DB, page, size int) *gorm.DB {
if page <= 0 {
page = 1
}
if size <= 0 || size > 100 {
size = 10
}
offset := (page - 1) * size
return db.Offset(offset).Limit(size)
}
func main() {
db, err := gorm.Open(sqlite.Open("crud.db"), &gorm.Config{})
if err != nil {
log.Fatal(err)
}
db.AutoMigrate(&User{})
// 准备 15 条数据
for i := 1; i <= 15; i++ {
db.Create(&User{Name: fmt.Sprintf("user_%d", i), Age: 20 + i})
}
// 第一页,每页 10 条
var users []User
page(db.Model(&User{}), 1, 10).Find(&users)
fmt.Printf("第 1 页: %d 条\n", len(users))
// 第二页
page(db.Model(&User{}), 2, 10).Find(&users)
fmt.Printf("第 2 页: %d 条\n", len(users))
// 第三页(只有 5 条)
page(db.Model(&User{}), 3, 10).Find(&users)
fmt.Printf("第 3 页: %d 条\n", len(users))
// 取消 Limit 和 Offset(在同一事务中复用 db 时有用)
db.Limit(10).Find(&users).Limit(-1).Find(&users)
}分组与聚合
go
package main
import (
"fmt"
"log"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type Order struct {
ID uint `gorm:"primaryKey"`
UserName string `gorm:"type:varchar(50);index"`
Amount float64
Status string `gorm:"type:varchar(20)"`
}
func main() {
db, err := gorm.Open(sqlite.Open("crud.db"), &gorm.Config{})
if err != nil {
log.Fatal(err)
}
db.AutoMigrate(&Order{})
// 准备数据
orders := []Order{
{UserName: "Tom", Amount: 100, Status: "paid"},
{UserName: "Tom", Amount: 200, Status: "paid"},
{UserName: "Jerry", Amount: 150, Status: "paid"},
{UserName: "Jerry", Amount: 50, Status: "unpaid"},
{UserName: "Tom", Amount: 80, Status: "unpaid"},
}
db.Create(&orders)
type Result struct {
UserName string
Total float64
Count int64
PaidTotal float64
}
// Group + Having + 聚合函数
var results []Result
db.Model(&Order{}).
Select("user_name, SUM(amount) as total, COUNT(*) as count, SUM(CASE WHEN status = 'paid' THEN amount ELSE 0 END) as paid_total").
Group("user_name").
Having("COUNT(*) > ?", 1).
Scan(&results)
for _, r := range results {
fmt.Printf("用户: %s, 总额: %.0f, 订单数: %d, 已付: %.0f\n",
r.UserName, r.Total, r.Count, r.PaidTotal)
}
// 单独的 Count
var count int64
db.Model(&Order{}).Where("status = ?", "paid").Count(&count)
fmt.Printf("已支付订单数: %d\n", count)
// 单独的 Sum
var sum float64
db.Model(&Order{}).Where("status = ?", "paid").Sum("amount", &sum)
fmt.Printf("已支付总额: %.0f\n", sum)
}去重 Distinct
go
package main
import (
"fmt"
"log"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type User struct {
ID uint `gorm:"primaryKey"`
Name string
City string
}
func main() {
db, err := gorm.Open(sqlite.Open("crud.db"), &gorm.Config{})
if err != nil {
log.Fatal(err)
}
db.AutoMigrate(&User{})
db.Create(&User{Name: "A", City: "Beijing"})
db.Create(&User{Name: "B", City: "Shanghai"})
db.Create(&User{Name: "C", City: "Beijing"})
// 查询不重复的城市
var cities []string
db.Model(&User{}).Distinct("city").Pluck("city", &cities)
fmt.Println("不重复的城市:", cities)
// 多字段 Distinct
type CityUser struct {
City string
Name string
}
var cityUsers []CityUser
db.Model(&User{}).Distinct("city", "name").Scan(&cityUsers)
fmt.Println("城市+用户组合数:", len(cityUsers))
}更新记录
Save 保存整个对象
Save 会保存所有字段,包括零值字段:
go
package main
import (
"fmt"
"log"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type User struct {
ID uint `gorm:"primaryKey"`
Name string
Email string
Age int
Status int `gorm:"default:1"`
}
func main() {
db, err := gorm.Open(sqlite.Open("crud.db"), &gorm.Config{})
if err != nil {
log.Fatal(err)
}
db.AutoMigrate(&User{})
user := User{Name: "Tom", Email: "tom@x.com", Age: 25, Status: 1}
db.Create(&user)
// Save 会保存所有字段
user.Age = 26
user.Status = 0 // 零值也会被保存
db.Save(&user)
var found User
db.First(&found, user.ID)
fmt.Printf("Age: %d, Status: %d\n", found.Age, found.Status)
}Update 单字段
go
package main
import (
"fmt"
"log"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type User struct {
ID uint `gorm:"primaryKey"`
Name string
Email string
Age int
Status int
}
func main() {
db, err := gorm.Open(sqlite.Open("crud.db"), &gorm.Config{})
if err != nil {
log.Fatal(err)
}
db.AutoMigrate(&User{})
user := User{Name: "Tom", Email: "tom@x.com", Age: 25, Status: 1}
db.Create(&user)
// Update 单个字段
db.Model(&user).Update("age", 30)
fmt.Printf("Update 后 Age: %d\n", user.Age)
// 条件更新
db.Model(&User{}).Where("name = ?", "Tom").Update("status", 0)
var found User
db.First(&found, user.ID)
fmt.Printf("Age: %d, Status: %d\n", found.Age, found.Status)
}Updates 多字段
go
package main
import (
"fmt"
"log"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type User struct {
ID uint `gorm:"primaryKey"`
Name string
Email string
Age int
Status int
}
func main() {
db, err := gorm.Open(sqlite.Open("crud.db"), &gorm.Config{})
if err != nil {
log.Fatal(err)
}
db.AutoMigrate(&User{})
user := User{Name: "Tom", Email: "tom@x.com", Age: 25, Status: 1}
db.Create(&user)
// 结构体更新:零值字段被忽略
db.Model(&user).Updates(struct {
Age int
Status int
}{Age: 30, Status: 0})
// Status=0 不会被更新,因为是零值
var found User
db.First(&found, user.ID)
fmt.Printf("结构体更新后 Age: %d, Status: %d(零值被忽略)\n", found.Age, found.Status)
}更新零值问题
结构体更新会忽略零值字段,这是常见陷阱。解决方法:使用 map 更新,或者用 Select 指定要更新的字段。
go
package main
import (
"fmt"
"log"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type User struct {
ID uint `gorm:"primaryKey"`
Name string
Email string
Age int
Status int
}
func main() {
db, err := gorm.Open(sqlite.Open("crud.db"), &gorm.Config{})
if err != nil {
log.Fatal(err)
}
db.AutoMigrate(&User{})
user := User{Name: "Tom", Email: "tom@x.com", Age: 25, Status: 1}
db.Create(&user)
// 方法1:使用 Map 更新,零值会被写入
db.Model(&user).Updates(map[string]interface{}{
"age": 30,
"status": 0, // 零值也会被更新
})
var found1 User
db.First(&found1, user.ID)
fmt.Printf("Map 更新后 Status: %d\n", found1.Status)
// 方法2:使用 Select 指定字段
db.Model(&user).Select("status").Updates(struct{ Status int }{Status: 1})
var found2 User
db.First(&found2, user.ID)
fmt.Printf("Select 更新后 Status: %d\n", found2.Status)
}删除记录
Delete 单条删除
go
package main
import (
"fmt"
"log"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type User struct {
ID uint `gorm:"primaryKey"`
Name string
Age int
}
func main() {
db, err := gorm.Open(sqlite.Open("crud.db"), &gorm.Config{})
if err != nil {
log.Fatal(err)
}
db.AutoMigrate(&User{})
db.Create(&User{Name: "Tom", Age: 25})
var user User
db.First(&user, 1)
// 删除单条
db.Delete(&user)
var count int64
db.Model(&User{}).Count(&count)
fmt.Printf("删除后剩余: %d 条\n", count)
}批量删除
go
package main
import (
"fmt"
"log"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type User struct {
ID uint `gorm:"primaryKey"`
Name string
Age int
}
func main() {
db, err := gorm.Open(sqlite.Open("crud.db"), &gorm.Config{})
if err != nil {
log.Fatal(err)
}
db.AutoMigrate(&User{})
for i := 1; i <= 10; i++ {
db.Create(&User{Name: fmt.Sprintf("user_%d", i), Age: 20 + i})
}
// 条件批量删除
result := db.Where("age > ?", 25).Delete(&User{})
fmt.Printf("删除 %d 条\n", result.RowsAffected)
// 按主键切片删除
db.Delete(&User{}, []int{1, 2, 3})
}软删除与 Unscoped
如果模型包含 gorm.DeletedAt 字段,Delete 不会真正删除记录,而是设置 DeletedAt。查询时默认会过滤掉已软删除的记录。
go
package main
import (
"fmt"
"log"
"time"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type User struct {
ID uint `gorm:"primaryKey"`
Name string
Age int
DeletedAt gorm.DeletedAt `gorm:"index"`
}
func main() {
db, err := gorm.Open(sqlite.Open("crud.db"), &gorm.Config{})
if err != nil {
log.Fatal(err)
}
db.AutoMigrate(&User{})
db.Create(&User{Name: "Tom", Age: 25})
db.Create(&User{Name: "Jerry", Age: 30})
// 软删除 Tom
var tom User
db.Where("name = ?", "Tom").First(&tom)
db.Delete(&tom)
// 普通查询:找不到已删除的
var activeUsers []User
db.Find(&activeUsers)
fmt.Printf("活跃用户数: %d(只剩 Jerry)\n", len(activeUsers))
// Unscoped:查询所有,包括软删除的
var allUsers []User
db.Unscoped().Find(&allUsers)
fmt.Printf("所有用户数: %d(包含 Tom)\n", len(allUsers))
// Unscoped:永久删除
db.Unscoped().Where("name = ?", "Tom").Delete(&User{})
var finalUsers []User
db.Unscoped().Find(&finalUsers)
fmt.Printf("永久删除后用户数: %d\n", len(finalUsers))
_ = time.Now
}完整示例:用户管理 CRUD
下面用一个完整的用户管理示例综合演示 CRUD 全流程:
go
package main
import (
"errors"
"fmt"
"log"
"time"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type User struct {
ID uint `gorm:"primaryKey" json:"id"`
Name string `gorm:"type:varchar(50);not null;index" json:"name"`
Email string `gorm:"type:varchar(150);uniqueIndex;not null" json:"email"`
Age int `gorm:"default:0" json:"age"`
Status int `gorm:"default:1" json:"status"` // 1启用 0禁用
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}
type UserService struct {
db *gorm.DB
}
func NewUserService(db *gorm.DB) *UserService {
return &UserService{db: db}
}
// Create 创建用户
func (s *UserService) Create(name, email string, age int) (*User, error) {
user := &User{Name: name, Email: email, Age: age, Status: 1}
if err := s.db.Create(user).Error; err != nil {
return nil, fmt.Errorf("创建用户失败: %w", err)
}
return user, nil
}
// GetByID 根据 ID 查询
func (s *UserService) GetByID(id uint) (*User, error) {
var user User
if err := s.db.First(&user, id).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, fmt.Errorf("用户不存在")
}
return nil, err
}
return &user, nil
}
// List 分页查询
func (s *UserService) List(page, size int, keyword string) ([]User, int64, error) {
if page <= 0 {
page = 1
}
if size <= 0 || size > 100 {
size = 10
}
offset := (page - 1) * size
var users []User
var total int64
query := s.db.Model(&User{})
if keyword != "" {
query = query.Where("name LIKE ?", "%"+keyword+"%")
}
query.Count(&total)
if err := query.Order("id desc").Offset(offset).Limit(size).Find(&users).Error; err != nil {
return nil, 0, err
}
return users, total, nil
}
// Update 更新用户(使用 map 避免零值问题)
func (s *UserService) Update(id uint, updates map[string]interface{}) error {
result := s.db.Model(&User{}).Where("id = ?", id).Updates(updates)
if result.Error != nil {
return result.Error
}
if result.RowsAffected == 0 {
return fmt.Errorf("用户不存在或无更新")
}
return nil
}
// Disable 禁用用户
func (s *UserService) Disable(id uint) error {
return s.Update(id, map[string]interface{}{"status": 0})
}
// Enable 启用用户
func (s *UserService) Enable(id uint) error {
return s.Update(id, map[string]interface{}{"status": 1})
}
// Delete 软删除
func (s *UserService) Delete(id uint) error {
return s.db.Delete(&User{}, id).Error
}
// HardDelete 永久删除
func (s *UserService) HardDelete(id uint) error {
return s.db.Unscoped().Delete(&User{}, id).Error
}
func main() {
db, err := gorm.Open(sqlite.Open("user_mgr.db"), &gorm.Config{})
if err != nil {
log.Fatal(err)
}
db.AutoMigrate(&User{})
svc := NewUserService(db)
// 创建
u1, _ := svc.Create("张三", "zhangsan@example.com", 25)
u2, _ := svc.Create("李四", "lisi@example.com", 30)
u3, _ := svc.Create("王五", "wangwu@example.com", 28)
fmt.Printf("创建用户: %s(id=%d), %s(id=%d), %s(id=%d)\n",
u1.Name, u1.ID, u2.Name, u2.ID, u3.Name, u3.ID)
// 查询单个
found, _ := svc.GetByID(u1.ID)
fmt.Printf("查询到: %s, 邮箱: %s\n", found.Name, found.Email)
// 分页查询
users, total, _ := svc.List(1, 2, "")
fmt.Printf("分页查询第1页: 共%d条, 当前页%d条\n", total, len(users))
for _, u := range users {
fmt.Printf(" - %s (age=%d)\n", u.Name, u.Age)
}
// 关键字搜索
searched, _, _ := svc.List(1, 10, "张")
fmt.Printf("搜索'张': %d 条\n", len(searched))
// 更新
svc.Update(u1.ID, map[string]interface{}{"age": 26, "status": 0})
updated, _ := svc.GetByID(u1.ID)
fmt.Printf("更新后: age=%d, status=%d\n", updated.Age, updated.Status)
// 禁用 / 启用
svc.Enable(u1.ID)
updated, _ = svc.GetByID(u1.ID)
fmt.Printf("启用后 status=%d\n", updated.Status)
// 软删除
svc.Delete(u3.ID)
users, total, _ = svc.List(1, 10, "")
fmt.Printf("软删除后: 共%d条\n", total)
// Unscoped 查询软删除
var allUsers []User
db.Unscoped().Find(&allUsers)
fmt.Printf("包含软删除: %d 条\n", len(allUsers))
}小结
本篇系统讲解了 GORM 的 CRUD 操作:
- 创建:
Create支持单条、批量、指定字段创建 - 读取:
First/Take/Last/Find,注意ErrRecordNotFound的差异 - 查询条件:字符串、结构体(忽略零值)、Map(包含零值)三种方式
- Select/Omit:精确控制查询字段,减少数据传输
- Order/Limit/Offset:排序与分页
- Group/Having/Count/Sum:分组聚合统计
- Distinct:去重查询
- 更新:
Save全字段、Update单字段、Updates多字段 - 零值陷阱:结构体更新会忽略零值,使用
map或Select解决 - 删除:默认软删除,
Unscoped查询或永久删除
下一篇我们将深入 GORM 的关联关系,包括一对一、一对多、多对多及预加载等高级特性。