--- title: GORM数据库 icon: mdi:database date: 2025-12-23 category: - Go - 后端 - 数据库 - ORM - GORM tag: - GORM - SQLite - MySQL - PostgreSQL - CRUD --- GORM 是 Go 最流行的 ORM 库,提供完善的数据库操作能力。本文通过完整可运行的示例,并结合 JPA/Hibernate 对比,帮助你快速掌握 GORM 使用。 --- # GORM 数据库操作:模型定义、CRUD、查询 > **Java 对比**:GORM 类似 JPA/Hibernate,但更轻量且代码更简洁。 --- ## 一、项目初始化 ### 1.1 创建项目 ```bash 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`: ```bash nano db_connect.go # 将以下代码复制粘贴到文件中,然后按 Ctrl+O 保存,Ctrl+X 退出 ``` ```go 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") } ``` ### 运行 & 测试 ```bash go run db_connect.go # Database connected successfully! # Connection pool configured ``` ### 2.2 连接 MySQL(可选) 创建 `db_connect_mysql.go`: ```bash nano db_connect_mysql.go # 将以下代码复制粘贴到文件中,然后按 Ctrl+O 保存,Ctrl+X 退出 ``` ```go 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`: ```bash nano model_basic.go # 将以下代码复制粘贴到文件中,然后按 Ctrl+O 保存,Ctrl+X 退出 ``` ```go 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!") } ``` ### 运行 & 测试 ```bash 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`: ```bash nano crud_create.go # 将以下代码复制粘贴到文件中,然后按 Ctrl+O 保存,Ctrl+X 退出 ``` ```go 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!") } ``` ### 运行 & 测试 ```bash 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`: ```bash nano crud_read.go # 将以下代码复制粘贴到文件中,然后按 Ctrl+O 保存,Ctrl+X 退出 ``` ```go 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)) } ``` ### 运行 & 测试 ```bash 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`: ```bash nano crud_update.go # 将以下代码复制粘贴到文件中,然后按 Ctrl+O 保存,Ctrl+X 退出 ``` ```go 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) } ``` ### 运行 & 测试 ```bash 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`: ```bash nano crud_delete.go # 将以下代码复制粘贴到文件中,然后按 Ctrl+O 保存,Ctrl+X 退出 ``` ```go 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) } ``` ### 运行 & 测试 ```bash 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`: ```bash nano query_advanced.go # 将以下代码复制粘贴到文件中,然后按 Ctrl+O 保存,Ctrl+X 退出 ``` ```go 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) } } ``` ### 运行 & 测试 ```bash 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`: ```bash nano raw_sql.go # 将以下代码复制粘贴到文件中,然后按 Ctrl+O 保存,Ctrl+X 退出 ``` ```go 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) } ``` ### 运行 & 测试 ```bash 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: ```go if result := db.First(&user, id); result.Error != nil { if result.Error == gorm.ErrRecordNotFound { // 记录不存在 } } ``` --- ## 九、下一步 ✅ 已掌握 GORM 基础操作 → 下一章:**GORM 事务与关联**(一对多、多对多) → 再下一章:**配置管理**(Viper) 祝你编码愉快!🚀