Files
MyBlog/src/programming/backend/go/Web开发数据库/17GORM数据库.md
liumangmang 67c0cbf0d7 feat(sidebar): 添加 Web开发数据库 侧边栏模块
- 在侧边栏配置中新增 Web开发数据库分类
- 增加相关文档链接,包括 Gin 框架、GORM、Viper、Zap 等内容
- 设置分类可折叠,提升导航体验

refactor(go): 精简 Go 示例代码导入包

- 移除未使用的 runtime 和 time 包
- 保留 fmt 和 sync 包,优化依赖

docs(linux): 删除 Nginx 基础入门文档

- 移除包含 Nginx 基本概念、安装、配置和常用命令的完整文档
- 清理废弃内容,简化项目文档结构
2025-12-24 17:23:17 +08:00

19 KiB
Raw Blame History

title, icon, date, category, tag
title icon date category tag
GORM数据库 mdi:database 2025-12-23
Go
后端
数据库
ORM
GORM
GORM
SQLite
MySQL
PostgreSQL
CRUD

GORM 是 Go 最流行的 ORM 库,提供完善的数据库操作能力。本文通过完整可运行的示例,并结合 JPA/Hibernate 对比,帮助你快速掌握 GORM 使用。


GORM 数据库操作模型定义、CRUD、查询

Java 对比GORM 类似 JPA/Hibernate但更轻量且代码更简洁。


一、项目初始化

1.1 创建项目

cd ~/GolandProjects
mkdir go-gorm-demo && cd go-gorm-demo
go mod init go-gorm-demo

# 安装 GORM 和驱动(指定兼容版本)
go get gorm.io/gorm@v1.25.5
go get gorm.io/driver/sqlite@v1.5.4   # SQLite
go get gorm.io/driver/mysql@v1.5.2    # MySQL可选
go get gorm.io/driver/postgres@v1.5.4 # PostgreSQL可选

版本说明

  • 所有示例代码兼容 Go 1.18-1.22 版本
  • GORM v1.25.5 已在 Go 1.22.2 上测试通过
  • 如需使用最新版本:go get -u gorm.io/gorm

二、数据库连接

2.1 连接 SQLite

创建 db_connect.go

nano db_connect.go
# 将以下代码复制粘贴到文件中,然后按 Ctrl+O 保存Ctrl+X 退出
package main

import (
    "fmt"
    "gorm.io/driver/sqlite"
    "gorm.io/gorm"
)

func main() {
    // 连接 SQLite文件数据库
    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    if err != nil {
        panic("failed to connect database")
    }
    
    fmt.Println("Database connected successfully!")
    
    // 获取底层的 *sql.DB
    sqlDB, err := db.DB()
    if err != nil {
        panic(err)
    }
    
    // 设置连接池
    sqlDB.SetMaxIdleConns(10)
    sqlDB.SetMaxOpenConns(100)
    
    fmt.Println("Connection pool configured")
}

运行 & 测试

go run db_connect.go
# Database connected successfully!
# Connection pool configured

2.2 连接 MySQL可选

创建 db_connect_mysql.go

nano db_connect_mysql.go
# 将以下代码复制粘贴到文件中,然后按 Ctrl+O 保存Ctrl+X 退出
package main

import (
    "fmt"
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
)

func main() {
    // MySQL DSN 格式: user:password@tcp(host:port)/dbname?charset=utf8mb4&parseTime=True&loc=Local
    dsn := "root:password@tcp(127.0.0.1:3306)/testdb?charset=utf8mb4&parseTime=True&loc=Local"
    
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        panic("failed to connect database")
    }
    
    fmt.Println("MySQL connected successfully!")
}

Java 对比

  • GORM 连接 ≈ JPA 的 EntityManagerFactory
  • gorm.Open() ≈ Hibernate 的 SessionFactory

三、模型定义

3.1 基础模型

创建 model_basic.go

nano model_basic.go
# 将以下代码复制粘贴到文件中,然后按 Ctrl+O 保存Ctrl+X 退出
package main

import (
    "fmt"
    "time"
    "gorm.io/driver/sqlite"
    "gorm.io/gorm"
)

// User 模型
type User struct {
    ID        uint      `gorm:"primaryKey"`
    Name      string    `gorm:"size:100;not null"`
    Email     string    `gorm:"size:100;unique;not null"`
    Age       int       `gorm:"default:0"`
    Active    bool      `gorm:"default:true"`
    CreatedAt time.Time
    UpdatedAt time.Time
}

func main() {
    db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    
    // 自动迁移(创建表)
    db.AutoMigrate(&User{})
    
    fmt.Println("Table created successfully!")
}

运行 & 测试

go run model_basic.go
# Table created successfully!

3.2 常用 Tag 说明

Tag 说明 Java/JPA 对比
primaryKey 主键 @Id
autoIncrement 自增 @GeneratedValue
size:100 字段长度 @Column(length=100)
unique 唯一约束 @Column(unique=true)
not null 非空约束 @Column(nullable=false)
default:value 默认值 @Column(columnDefinition="...")
index 索引 @Index
- 忽略字段 @Transient

Java 对比

  • GORM Tag ≈ JPA 注解(@Entity, @Column
  • AutoMigrate ≈ Hibernate 的 hbm2ddl.auto=update

四、CRUD 操作

4.1 Create - 创建数据

创建 crud_create.go

nano crud_create.go
# 将以下代码复制粘贴到文件中,然后按 Ctrl+O 保存Ctrl+X 退出
package main

import (
    "fmt"
    "time"
    "gorm.io/driver/sqlite"
    "gorm.io/gorm"
)

type User struct {
    ID        uint      `gorm:"primaryKey"`
    Name      string    `gorm:"size:100"`
    Email     string    `gorm:"size:100;unique"`
    Age       int
    CreatedAt time.Time
}

func main() {
    db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    db.AutoMigrate(&User{})
    
    // 1. 单条插入
    user := User{
        Name:  "Alice",
        Email: "alice@example.com",
        Age:   30,
    }
    result := db.Create(&user)
    
    if result.Error != nil {
        panic(result.Error)
    }
    
    fmt.Printf("New user ID: %d\n", user.ID)
    fmt.Printf("Rows affected: %d\n", result.RowsAffected)
    
    // 2. 批量插入
    users := []User{
        {Name: "Bob", Email: "bob@example.com", Age: 25},
        {Name: "Charlie", Email: "charlie@example.com", Age: 35},
        {Name: "David", Email: "david@example.com", Age: 28},
    }
    
    db.Create(users)
    fmt.Println("Batch insert completed!")
    
    // 3. 指定字段插入
    user2 := User{Name: "Eve", Email: "eve@example.com", Age: 22}
    db.Select("name", "email").Create(&user2)  // 仅插入 name 和 email
    
    fmt.Println("All users created successfully!")
}

运行 & 测试

go run crud_create.go
# New user ID: 1
# Rows affected: 1
# Batch insert completed!
# All users created successfully!

Java 对比

  • db.Create()entityManager.persist()
  • 批量插入 ≈ saveAll()

4.2 Read - 查询数据

创建 crud_read.go

nano crud_read.go
# 将以下代码复制粘贴到文件中,然后按 Ctrl+O 保存Ctrl+X 退出
package main

import (
    "fmt"
    "time"
    "gorm.io/driver/sqlite"
    "gorm.io/gorm"
)

type User struct {
    ID        uint   `gorm:"primaryKey"`
    Name      string `gorm:"size:100"`
    Email     string `gorm:"size:100;unique"`
    Age       int
    CreatedAt time.Time
}

func main() {
    db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    
    // 先插入测试数据
    db.AutoMigrate(&User{})
    db.Create(&User{Name: "Alice", Email: "alice@example.com", Age: 30})
    db.Create(&User{Name: "Bob", Email: "bob@example.com", Age: 25})
    db.Create(&User{Name: "Charlie", Email: "charlie@example.com", Age: 35})
    
    // 1. 根据 ID 查询
    var user User
    db.First(&user, 1)  // 查询 ID=1 的用户
    fmt.Printf("User ID=1: %s (%s)\n", user.Name, user.Email)
    
    // 2. 按条件查询单条
    var user2 User
    db.Where("email = ?", "bob@example.com").First(&user2)
    fmt.Printf("User by email: %s (age=%d)\n", user2.Name, user2.Age)
    
    // 3. 查询所有
    var users []User
    db.Find(&users)
    fmt.Printf("Total users: %d\n", len(users))
    
    // 4. 按条件查询多条
    var adults []User
    db.Where("age >= ?", 30).Find(&adults)
    fmt.Printf("Users age >= 30: %d\n", len(adults))
    
    // 5. 使用 IN 查询
    var selectedUsers []User
    db.Where("id IN ?", []int{1, 2}).Find(&selectedUsers)
    fmt.Printf("Selected users: %d\n", len(selectedUsers))
    
    // 6. 排序查询
    var sortedUsers []User
    db.Order("age DESC").Find(&sortedUsers)
    fmt.Println("Users sorted by age (DESC):")
    for _, u := range sortedUsers {
        fmt.Printf("  - %s (age=%d)\n", u.Name, u.Age)
    }
    
    // 7. 分页查询
    var pageUsers []User
    db.Offset(0).Limit(2).Find(&pageUsers)
    fmt.Printf("Page 1 (limit 2): %d users\n", len(pageUsers))
    
    // 8. 组合查询
    var combinedUsers []User
    db.Where("age > ?", 25).
       Order("age DESC").
       Limit(2).
       Find(&combinedUsers)
    fmt.Printf("Combined query: %d users\n", len(combinedUsers))
}

运行 & 测试

go run crud_read.go
# User ID=1: Alice (alice@example.com)
# User by email: Bob (age=25)
# Total users: 3
# Users age >= 30: 2
# Selected users: 2
# Users sorted by age (DESC):
#   - Charlie (age=35)
#   - Alice (age=30)
#   - Bob (age=25)
# Page 1 (limit 2): 2 users
# Combined query: 2 users

Java 对比

  • db.Find()findAll()
  • db.Where()@Query("WHERE ...")
  • db.Order()Sort.by()
  • db.Limit()Pageable.ofSize()

4.3 Update - 更新数据

创建 crud_update.go

nano crud_update.go
# 将以下代码复制粘贴到文件中,然后按 Ctrl+O 保存Ctrl+X 退出
package main

import (
    "fmt"
    "time"
    "gorm.io/driver/sqlite"
    "gorm.io/gorm"
)

type User struct {
    ID        uint   `gorm:"primaryKey"`
    Name      string `gorm:"size:100"`
    Email     string `gorm:"size:100;unique"`
    Age       int
    UpdatedAt time.Time
}

func main() {
    db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    db.AutoMigrate(&User{})
    
    // 插入测试数据
    user := User{Name: "Alice", Email: "alice@example.com", Age: 30}
    db.Create(&user)
    
    // 1. 更新单个字段
    db.Model(&User{}).Where("id = ?", user.ID).Update("name", "Alice Updated")
    fmt.Println("Updated single field")
    
    // 2. 更新多个字段(使用 map
    db.Model(&User{}).Where("id = ?", user.ID).Updates(map[string]interface{}{
        "name": "Alice Smith",
        "age":  31,
    })
    fmt.Println("Updated multiple fields (map)")
    
    // 3. 更新多个字段(使用 struct仅更新非零值
    db.Model(&User{}).Where("id = ?", user.ID).Updates(User{
        Name: "Alice Johnson",
        Age:  32,
    })
    fmt.Println("Updated multiple fields (struct)")
    
    // 4. 强制更新零值字段
    db.Model(&User{}).Where("id = ?", user.ID).Update("age", 0)
    fmt.Println("Updated age to zero")
    
    // 5. 批量更新
    db.Model(&User{}).Where("age > ?", 25).Update("age", gorm.Expr("age + ?", 1))
    fmt.Println("Batch update completed")
    
    // 查询最终结果
    var finalUser User
    db.First(&finalUser, user.ID)
    fmt.Printf("Final user: %s (age=%d)\n", finalUser.Name, finalUser.Age)
}

运行 & 测试

go run crud_update.go
# Updated single field
# Updated multiple fields (map)
# Updated multiple fields (struct)
# Updated age to zero
# Batch update completed
# Final user: Alice Johnson (age=0)

Java 对比

  • db.Updates()entityManager.merge()
  • 批量更新 ≈ @Modifying @Query("UPDATE ...")

4.4 Delete - 删除数据

创建 crud_delete.go

nano crud_delete.go
# 将以下代码复制粘贴到文件中,然后按 Ctrl+O 保存Ctrl+X 退出
package main

import (
    "fmt"
    "time"
    "gorm.io/driver/sqlite"
    "gorm.io/gorm"
)

type User struct {
    ID        uint           `gorm:"primaryKey"`
    Name      string         `gorm:"size:100"`
    Email     string         `gorm:"size:100;unique"`
    Age       int
    DeletedAt gorm.DeletedAt `gorm:"index"`  // 软删除字段
}

func main() {
    db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    db.AutoMigrate(&User{})
    
    // 插入测试数据
    users := []User{
        {Name: "Alice", Email: "alice@example.com", Age: 30},
        {Name: "Bob", Email: "bob@example.com", Age: 25},
        {Name: "Charlie", Email: "charlie@example.com", Age: 35},
    }
    db.Create(users)
    
    // 1. 软删除(标记 deleted_at
    db.Delete(&User{}, 1)  // 删除 ID=1 的用户
    fmt.Println("Soft deleted user ID=1")
    
    // 2. 查询时默认不包括已删除的
    var activeUsers []User
    db.Find(&activeUsers)
    fmt.Printf("Active users: %d\n", len(activeUsers))
    
    // 3. 查询所有记录(包括已删除的)
    var allUsers []User
    db.Unscoped().Find(&allUsers)
    fmt.Printf("All users (including deleted): %d\n", len(allUsers))
    
    // 4. 永久删除(硬删除)
    db.Unscoped().Delete(&User{}, 2)
    fmt.Println("Permanently deleted user ID=2")
    
    // 5. 批量删除
    db.Where("age < ?", 30).Delete(&User{})
    fmt.Println("Batch delete completed")
    
    // 最终统计
    var finalCount int64
    db.Model(&User{}).Count(&finalCount)
    fmt.Printf("Final active users: %d\n", finalCount)
}

运行 & 测试

go run crud_delete.go
# Soft deleted user ID=1
# Active users: 2
# All users (including deleted): 3
# Permanently deleted user ID=2
# Batch delete completed
# Final active users: 1

Java 对比

  • 软删除 ≈ 使用 @SQLDelete + @Where
  • DeletedAt ≈ JPA 的逻辑删除注解
  • 硬删除 ≈ entityManager.remove()

五、高级查询

5.1 复杂查询

创建 query_advanced.go

nano query_advanced.go
# 将以下代码复制粘贴到文件中,然后按 Ctrl+O 保存Ctrl+X 退出
package main

import (
    "fmt"
    "time"
    "gorm.io/driver/sqlite"
    "gorm.io/gorm"
)

type User struct {
    ID        uint   `gorm:"primaryKey"`
    Name      string `gorm:"size:100"`
    Email     string `gorm:"size:100;unique"`
    Age       int
    Active    bool   `gorm:"default:true"`
    CreatedAt time.Time
}

func main() {
    db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    db.AutoMigrate(&User{})
    
    // 插入测试数据
    users := []User{
        {Name: "Alice", Email: "alice@example.com", Age: 30, Active: true},
        {Name: "Bob", Email: "bob@example.com", Age: 25, Active: false},
        {Name: "Charlie", Email: "charlie@example.com", Age: 35, Active: true},
        {Name: "David", Email: "david@example.com", Age: 28, Active: true},
    }
    db.Create(users)
    
    // 1. 多条件查询
    var result1 []User
    db.Where("age > ? AND active = ?", 25, true).Find(&result1)
    fmt.Printf("Age > 25 AND active: %d users\n", len(result1))
    
    // 2. OR 查询
    var result2 []User
    db.Where("age < ? OR active = ?", 30, false).Find(&result2)
    fmt.Printf("Age < 30 OR inactive: %d users\n", len(result2))
    
    // 3. 模糊查询
    var result3 []User
    db.Where("name LIKE ?", "%li%").Find(&result3)
    fmt.Printf("Name contains 'li': %d users\n", len(result3))
    
    // 4. 仅查询指定字段
    var result4 []User
    db.Select("name", "email").Find(&result4)
    fmt.Printf("Selected fields: %d users\n", len(result4))
    for _, u := range result4 {
        fmt.Printf("  - %s (%s)\n", u.Name, u.Email)
    }
    
    // 5. 统计查询
    var count int64
    db.Model(&User{}).Where("active = ?", true).Count(&count)
    fmt.Printf("Active users count: %d\n", count)
    
    // 6. 聚合查询
    var avgAge float64
    db.Model(&User{}).Select("AVG(age)").Row().Scan(&avgAge)
    fmt.Printf("Average age: %.2f\n", avgAge)
    
    // 7. 分组查询
    type Result struct {
        Active bool
        Count  int64
    }
    var groupResult []Result
    db.Model(&User{}).Select("active, count(*) as count").Group("active").Scan(&groupResult)
    fmt.Println("Group by active:")
    for _, r := range groupResult {
        fmt.Printf("  Active=%v: %d users\n", r.Active, r.Count)
    }
}

运行 & 测试

go run query_advanced.go
# Age > 25 AND active: 2 users
# Age < 30 OR inactive: 2 users
# Name contains 'li': 2 users
# Selected fields: 4 users
#   - Alice (alice@example.com)
#   - Bob (bob@example.com)
#   - Charlie (charlie@example.com)
#   - David (david@example.com)
# Active users count: 3
# Average age: 29.50
# Group by active:
#   Active=false: 1 users
#   Active=true: 3 users

Java 对比

  • db.Where() ≈ JPA Criteria API 或 @Query
  • db.Count()countBy...
  • db.Group() ≈ JPQL 的 GROUP BY

六、原生 SQL

6.1 执行原生查询

创建 raw_sql.go

nano raw_sql.go
# 将以下代码复制粘贴到文件中,然后按 Ctrl+O 保存Ctrl+X 退出
package main

import (
    "fmt"
    "gorm.io/driver/sqlite"
    "gorm.io/gorm"
)

type User struct {
    ID    uint   `gorm:"primaryKey"`
    Name  string `gorm:"size:100"`
    Email string `gorm:"size:100;unique"`
    Age   int
}

func main() {
    db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    db.AutoMigrate(&User{})
    
    // 插入测试数据
    db.Create(&User{Name: "Alice", Email: "alice@example.com", Age: 30})
    db.Create(&User{Name: "Bob", Email: "bob@example.com", Age: 25})
    
    // 1. 原生查询
    var users []User
    db.Raw("SELECT * FROM users WHERE age > ?", 20).Scan(&users)
    fmt.Printf("Raw query result: %d users\n", len(users))
    
    // 2. 原生更新
    db.Exec("UPDATE users SET age = age + 1 WHERE name = ?", "Alice")
    fmt.Println("Raw update executed")
    
    // 3. 原生统计
    var count int64
    db.Raw("SELECT COUNT(*) FROM users").Scan(&count)
    fmt.Printf("Total users: %d\n", count)
    
    // 4. 查询单行
    var result struct {
        Name string
        Age  int
    }
    db.Raw("SELECT name, age FROM users WHERE id = ?", 1).Scan(&result)
    fmt.Printf("User: %s (age=%d)\n", result.Name, result.Age)
}

运行 & 测试

go run raw_sql.go
# Raw query result: 2 users
# Raw update executed
# Total users: 2
# User: Alice (age=31)

Java 对比

  • db.Raw()entityManager.createNativeQuery()
  • db.Exec()@Query(nativeQuery = true)

七、对比总结

功能 GORM JPA/Hibernate 说明
连接数据库 gorm.Open() EntityManagerFactory GORM 更简洁
模型定义 Struct + Tag @Entity + 注解 GORM 更轻量
创建 db.Create() persist() 语法不同
查询 db.Find() findAll() GORM 更灵活
更新 db.Updates() merge() GORM 支持链式
删除 db.Delete() remove() GORM 自带软删除
原生SQL db.Raw() createNativeQuery() 类似

八、常见问题

Q: GORM 如何防止 SQL 注入?

A: GORM 使用预编译语句prepared statements只要使用 ? 占位符就是安全的。

Q: 软删除和硬删除的区别?

A:

  • 软删除:仅标记 deleted_at 字段,数据仍在数据库中
  • 硬删除:使用 Unscoped().Delete() 永久删除数据

Q: 如何处理查询不存在的记录?

A:

if result := db.First(&user, id); result.Error != nil {
    if result.Error == gorm.ErrRecordNotFound {
        // 记录不存在
    }
}

九、下一步

已掌握 GORM 基础操作
→ 下一章:GORM 事务与关联(一对多、多对多)
→ 再下一章:配置管理Viper

祝你编码愉快!🚀