GORM数据库
2025/12/23大约 9 分钟
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 configured2.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 的
EntityManagerFactorygorm.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 usersJava 对比:
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: 1Java 对比:
- 软删除 ≈ 使用
@SQLDelete+@WhereDeletedAt≈ 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 usersJava 对比:
db.Where()≈ JPA Criteria API 或@Querydb.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)
祝你编码愉快!🚀
