【Go语言学习系列35】数据库编程(二):ORM技术

📚 原创系列: “Go语言学习系列”

🔄 转载说明: 本文最初发布于"Gopher部落"微信公众号,经原作者授权转载。

🔗 关注原创: 欢迎扫描文末二维码,关注"Gopher部落"微信公众号获取第一手Go技术文章。

📑 Go语言学习系列导航

本文是【Go语言学习系列】的第35篇,当前位于第三阶段(进阶篇)

🚀 第三阶段:进阶篇
  1. 并发编程(一):goroutine基础
  2. 并发编程(二):channel基础
  3. 并发编程(三):select语句
  4. 并发编程(四):sync包
  5. 并发编程(五):并发模式
  6. 并发编程(六):原子操作与内存模型
  7. 数据库编程(一):SQL接口
  8. 数据库编程(二):ORM技术 👈 当前位置
  9. Web开发(一):路由与中间件
  10. Web开发(二):模板与静态资源
  11. Web开发(三):API开发
  12. Web开发(四):认证与授权
  13. Web开发(五):WebSocket
  14. 微服务(一):基础概念
  15. 微服务(二):gRPC入门
  16. 日志与监控
  17. 第三阶段项目实战:微服务聊天应用

📚 查看完整Go语言学习系列导航

📖 文章导读

在本文中,您将了解:

  • ORM技术的基本概念及其在Go语言中的应用
  • 如何使用GORM与各种数据库交互
  • 定义模型和处理数据库迁移的方法
  • 基本的CRUD操作和高级查询技巧
  • 如何处理一对一、一对多、多对多等关联关系
  • 事务处理和钩子函数的应用
  • 避免N+1查询等性能问题的最佳实践
  • 通过博客系统实例展示ORM的实际应用

Go ORM技术

数据库编程(二):ORM技术

在上一篇文章中,我们学习了Go语言中的SQL接口,使用database/sql包进行数据库操作。虽然原生SQL提供了最大的灵活性和控制力,但在实际项目中,我们经常需要一种更便捷、更安全、更贴近业务逻辑的方式来操作数据库。这就是ORM(对象关系映射)技术的用武之地。

1. ORM概念介绍

1.1 什么是ORM?

ORM(Object-Relational Mapping,对象关系映射)是一种编程技术,它建立了编程语言中的对象与关系型数据库中表的映射关系,使开发者能够使用面向对象的方式来操作数据库,而无需直接编写SQL语句。

在ORM框架中:

  • 数据表映射为类/结构体
  • 表中的字段映射为结构体的字段
  • 表中的记录映射为结构体的实例
  • SQL操作映射为对象上的方法调用

1.2 ORM的优势

ORM为我们带来许多优势:

  1. 生产力提升:减少重复的CRUD(创建、读取、更新、删除)操作代码
  2. 面向对象的编程方式:使用对象和方法代替SQL语句
  3. 数据库抽象:减少与特定数据库系统的耦合
  4. 类型安全:编译时检查替代运行时SQL字符串拼接错误
  5. 安全性:减少SQL注入风险
  6. 自动处理关联关系:方便处理一对一、一对多、多对多等关系
  7. 内置迁移工具:简化数据库结构变更管理

1.3 ORM的劣势

ORM也有一些潜在的缺点:

  1. 性能开销:在简单查询上可能比原生SQL慢
  2. 学习曲线:需要学习ORM框架的特定API
  3. 复杂查询限制:有些复杂SQL查询可能难以用ORM表达
  4. “抽象泄漏”:在某些情况下,隐藏的数据库细节可能会影响应用行为
  5. 过度使用可能导致非最优SQL:自动生成的SQL可能不如手写的优化

1.4 Go语言中的主要ORM库

Go语言中有几个流行的ORM库:

  1. GORM:目前最流行的Go语言ORM库,功能全面且活跃维护
  2. XORM:另一个成熟的ORM库,具有良好的性能
  3. Ent:Facebook开发的实体框架,使用代码生成的方式
  4. SQLBoiler:以生成代码为主的ORM,专注于类型安全
  5. 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作为主键
  • CreatedAtUpdatedAtDeletedAt字段会被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(
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Gopher部落

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值