📚 原创系列: “Go语言学习系列”
🔄 转载说明: 本文最初发布于"Gopher部落"微信公众号,经原作者授权转载。
🔗 关注原创: 欢迎扫描文末二维码,关注"Gopher部落"微信公众号获取第一手Go技术文章。
📑 Go语言学习系列导航
🚀 第三阶段:进阶篇本文是【Go语言学习系列】的第35篇,当前位于第三阶段(进阶篇)
- 并发编程(一):goroutine基础
- 并发编程(二):channel基础
- 并发编程(三):select语句
- 并发编程(四):sync包
- 并发编程(五):并发模式
- 并发编程(六):原子操作与内存模型
- 数据库编程(一):SQL接口
- 数据库编程(二):ORM技术 👈 当前位置
- Web开发(一):路由与中间件
- Web开发(二):模板与静态资源
- Web开发(三):API开发
- Web开发(四):认证与授权
- Web开发(五):WebSocket
- 微服务(一):基础概念
- 微服务(二):gRPC入门
- 日志与监控
- 第三阶段项目实战:微服务聊天应用
📖 文章导读
在本文中,您将了解:
- ORM技术的基本概念及其在Go语言中的应用
- 如何使用GORM与各种数据库交互
- 定义模型和处理数据库迁移的方法
- 基本的CRUD操作和高级查询技巧
- 如何处理一对一、一对多、多对多等关联关系
- 事务处理和钩子函数的应用
- 避免N+1查询等性能问题的最佳实践
- 通过博客系统实例展示ORM的实际应用

数据库编程(二):ORM技术
在上一篇文章中,我们学习了Go语言中的SQL接口,使用database/sql包进行数据库操作。虽然原生SQL提供了最大的灵活性和控制力,但在实际项目中,我们经常需要一种更便捷、更安全、更贴近业务逻辑的方式来操作数据库。这就是ORM(对象关系映射)技术的用武之地。
1. ORM概念介绍
1.1 什么是ORM?
ORM(Object-Relational Mapping,对象关系映射)是一种编程技术,它建立了编程语言中的对象与关系型数据库中表的映射关系,使开发者能够使用面向对象的方式来操作数据库,而无需直接编写SQL语句。
在ORM框架中:
- 数据表映射为类/结构体
- 表中的字段映射为结构体的字段
- 表中的记录映射为结构体的实例
- SQL操作映射为对象上的方法调用
1.2 ORM的优势
ORM为我们带来许多优势:
- 生产力提升:减少重复的CRUD(创建、读取、更新、删除)操作代码
- 面向对象的编程方式:使用对象和方法代替SQL语句
- 数据库抽象:减少与特定数据库系统的耦合
- 类型安全:编译时检查替代运行时SQL字符串拼接错误
- 安全性:减少SQL注入风险
- 自动处理关联关系:方便处理一对一、一对多、多对多等关系
- 内置迁移工具:简化数据库结构变更管理
1.3 ORM的劣势
ORM也有一些潜在的缺点:
- 性能开销:在简单查询上可能比原生SQL慢
- 学习曲线:需要学习ORM框架的特定API
- 复杂查询限制:有些复杂SQL查询可能难以用ORM表达
- “抽象泄漏”:在某些情况下,隐藏的数据库细节可能会影响应用行为
- 过度使用可能导致非最优SQL:自动生成的SQL可能不如手写的优化
1.4 Go语言中的主要ORM库
Go语言中有几个流行的ORM库:
- GORM:目前最流行的Go语言ORM库,功能全面且活跃维护
- XORM:另一个成熟的ORM库,具有良好的性能
- Ent:Facebook开发的实体框架,使用代码生成的方式
- SQLBoiler:以生成代码为主的ORM,专注于类型安全
- SQLx:介于原生SQL和ORM之间的库,提供便捷的查询构建器
在本文中,我们将主要介绍GORM,因为它是当前Go社区中使用最广泛的ORM库。
2. GORM入门
GORM是Go语言中最受欢迎的ORM库,提供了友好的API,并支持各种数据库,包括MySQL、PostgreSQL、SQLite和SQL Server等。
2.1 安装GORM
安装GORM非常简单,使用go get命令即可:
go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql # MySQL驱动
# 或其他数据库驱动
go get -u gorm.io/driver/postgres # PostgreSQL驱动
go get -u gorm.io/driver/sqlite # SQLite驱动
go get -u gorm.io/driver/sqlserver # SQL Server驱动
2.2 连接数据库
下面是连接MySQL数据库的示例:
package main
import (
"fmt"
"log"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
func main() {
// 连接MySQL数据库
dsn := "user:password@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info), // 设置日志级别
})
if err != nil {
log.Fatalf("无法连接到数据库: %v", err)
}
// 获取底层SQL连接以设置连接池参数
sqlDB, err := db.DB()
if err != nil {
log.Fatalf("无法获取数据库连接: %v", err)
}
// 设置连接池参数
sqlDB.SetMaxIdleConns(10) // 设置空闲连接池中连接的最大数量
sqlDB.SetMaxOpenConns(100) // 设置打开数据库连接的最大数量
sqlDB.SetConnMaxLifetime(time.Hour) // 设置了连接可复用的最大时间
fmt.Println("成功连接到数据库!")
}
GORM使用的数据库连接池由底层的*sql.DB实例管理,我们可以通过db.DB()方法获取这个实例并设置连接池参数。
2.3 定义模型
在GORM中,模型是与数据库表映射的结构体。我们通过定义Go结构体来创建数据库模型:
type User struct {
ID uint `gorm:"primarykey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
Name string `gorm:"size:255;not null"`
Email string `gorm:"size:255;uniqueIndex;not null"`
Age uint8 `gorm:"default:18"`
Address string `gorm:"type:varchar(500)"`
IsActive bool `gorm:"default:true"`
}
在上面的例子中:
- GORM约定使用
ID作为主键 CreatedAt、UpdatedAt和DeletedAt字段会被GORM自动管理- 通过结构体标签(tag)定义字段的数据库属性,如长度、唯一索引等
gorm.DeletedAt字段支持软删除功能
自定义表名
默认情况下,GORM会使用结构体名称的蛇形复数作为表名。例如,User结构体对应的表名是users。如果需要自定义表名,可以实现TableName方法:
// TableName 指定用户模型的表名
func (User) TableName() string {
return "my_users"
}
或者全局设置表名:
db.Config.NamingStrategy = schema.NamingStrategy{
TablePrefix: "t_", // 表名前缀
SingularTable: true, // 使用单数表名
}
2.4 自动迁移
GORM提供了自动迁移功能,它会自动创建表、缺失的外键、约束、列和索引,并且会更改现有列的类型(如果大小、精度、是否为空等发生变化)。但它不会删除未使用的列,以保护数据。
// 自动迁移
err := db.AutoMigrate(&User{
}, &Product{
}, &Order{
})
if err != nil {
log.Fatalf("自动迁移失败: %v", err)
}
注意:自动迁移只应在开发环境中使用。在生产环境中,应该使用数据库迁移工具,如golang-migrate或Atlas。
2.5 基本CRUD操作
现在我们了解了如何定义模型和连接数据库,接下来让我们看看如何使用GORM执行基本的CRUD(创建、读取、更新、删除)操作。
创建记录
// 创建一条记录
user := User{
Name: "张三", Email: "zhangsan@example.com", Age: 25}
result := db.Create(&user)
if result.Error != nil {
log.Fatalf("创建用户失败: %v", result.Error)
}
fmt.Printf("创建用户成功,ID: %d, 受影响的行数: %d\n", user.ID, result.RowsAffected)
// 批量创建
users := []User{
{
Name: "李四", Email: "lisi@example.com", Age: 30},
{
Name: "王五", Email: "wangwu@example.com", Age: 28},
}
result = db.Create(&users)
if result.Error != nil {
log.Fatalf("批量创建用户失败: %v", result.Error)
}
fmt.Printf("批量创建用户成功,受影响的行数: %d\n", result.RowsAffected)
查询记录
// 查询单条记录
var firstUser User
result := db.First(&firstUser) // 获取第一条记录(按主键排序)
if result.Error != nil {
log.Fatalf("查询失败: %v", result.Error)
}
fmt.Printf("第一条用户记录: %+v\n", firstUser)
// 根据主键查询
var user User
result = db.First(&user, 10) // 查找ID为10的用户
// 或者
result = db.First(&user, "id = ?", 10)
// 查询多条记录
var users []User
result = db.Find(&users) // 查询所有用户
fmt.Printf("总共查询到 %d 个用户\n", len(users))
// 条件查询
var activeUsers []User
result = db.Where("age > ? AND is_active = ?", 20, true).Find(&activeUsers)
// 或者使用结构体
result = db.Where(&User{
IsActive: true}).Where("age > ?", 20).Find(&activeUsers)
// 或者使用map
result = db.Where(map[string]interface{
}{
"is_active": true}).Find(&activeUsers)
// 排序
var orderedUsers []User
db.Order("age desc, name").Limit(10).Find(&orderedUsers)
// 分页
var pageUsers []User
var totalCount int64
db.Model(&User{
}).Count(&totalCount)
db.Offset(10).Limit(10).Find(&pageUsers) // 第二页,每页10条
// 选择特定字段
var partialUsers []User
db.Select("name", "email").Find(&partialUsers)
更新记录
// 保存所有字段
var user User
db.First(&user, 1)
user.Name = "新名字"
user.Age = 32
db.Save(&user) // 更新所有字段
// 更新单个字段
db.Model(&user).Update("Name", "更新的名字")
// 更新多个字段
db.Model(&user).Updates(User{
Name: "新名字", Age: 35}) // 只会更新非零值字段
// 或者使用map更新任意值,包括零值
db.Model(&user).Updates(map[string]interface{
}{
"name": "新名字", "age": 0, "is_active": false})
// 批量更新
db.Model(&User{
}).Where("age > ?", 30).Update("is_active", false)
删除记录
// 删除记录
var user User
db.First(&user, 1)
db.Delete(&user) // 软删除,会设置DeletedAt
// 根据主键删除
db.Delete(&User{
}, 10)
// 或者
db.Delete(&User{
}, []int{
1, 2, 3})
// 批量删除
db.Where("age < ?", 18).Delete(&User{
})
// 永久删除
db.Unscoped().Delete(&user) // 永久删除,不使用软删除
2.6 条件查询
GORM提供了丰富的条件查询方法:
// 基本条件
db.Where("name = ?", "张三").First(&user)
// NOT条件
db.Not("name = ?", "张三").Find(&users)
// OR条件
db.Where(

最低0.47元/天 解锁文章
2589

被折叠的 条评论
为什么被折叠?



