使用 GORM 连接数据库实践 | 豆包MarsCode AI刷题

40 阅读7分钟

在现代应用开发中,数据库操作是必不可少的环节。然而,手动编写数据库操作代码不仅繁琐,而且容易出错。幸运的是,GORM 作为 Go 语言的强大 ORM(对象关系映射)库,使得数据库操作变得更加简便和直观。本文将带你详细了解如何使用 GORM 连接数据库,并实现增删改查操作,帮助你更高效地进行数据库操作。

安装 GORM 和 MySQL 驱动

首先,我们需要安装 GORM 和 MySQL 驱动:

go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql

初始化数据库连接

创建一个名为 main.go 的文件,并在其中初始化数据库连接:

package main

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

func main() {
    // 数据库连接字符串
    dsn := "username:password@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
    
    // 打开数据库连接
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        log.Fatalf("无法连接数据库: %v", err)
    } else {
        log.Println("数据库连接成功!")
    }
}

MySQL 驱动程序提供了一些高级配置可以在初始化过程中使用,例如:

db, err := gorm.Open(mysql.New(mysql.Config{   
  DSN: "gorm:gorm@tcp(127.0.0.1:3306)/gorm?charset=utf8&parseTime=True&loc=Local", // DSN data source name   
  DefaultStringSize: 256, // string 类型字段的默认长度   
  DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持         DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引   
  DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列   
  SkipInitializeWithVersion: false, // 根据当前 MySQL 版本自动配置 
}), &gorm.Config{})

连接池

GORM 使用database/sql来维护连接池

sqlDB, err := db.DB()

// SetMaxIdleConns设置空闲连接池的最大连接数。 
sqlDB.SetMaxIdleConns(10)  

// SetMaxOpenConns 设置数据库的最大打开连接数。 
sqlDB.SetMaxOpenConns(100)  

// SetConnMaxLifetime 设置连接可重用的最大时间。
sqlDB.SetConnMaxLifetime(time.Hour)

定义模型

定义一个模型类,比如 User

type User struct { 
    ID uint `gorm:"primaryKey"` 
    Name string 
    Age int 
    Birthday time.Time 
}

创建记录

创建一条记录

user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}  
result := db.Create(&user) // 通过数据的指针来创建  
user.ID             // 返回插入数据的主键 
result.Error        // 返回 error 
result.RowsAffected // 返回插入记录的条数

我们还可以使用 Create() 创建多项记录:

users := []*User{     
    {Name: "Jinzhu", Age: 18, Birthday: time.Now()},     
    {Name: "Jackson", Age: 19, Birthday: time.Now()}, 
}  
result := db.Create(users) // 传递切片来插入多行  
result.Error        // 返回 error
result.RowsAffected // 返回插入记录的条数

GORM支持通过 map[string]interface{} 与 []map[string]interface{}{}来创建记录。

db.Model(&User{}).Create(map[string]interface{}{   
    "Name": "jinzhu", "Age": 18, 
})  

// 通过 `[]map[string]interface{}{}` 批量插入
db.Model(&User{}).Create([]map[string]interface{}{   
    {"Name": "jinzhu_1", "Age": 18},   
    {"Name": "jinzhu_2", "Age": 20}, 
})

我们还可以通过结构体Tag default来定义字段的默认值,示例如下:

type User struct {   
    ID   int64   
    Name string `gorm:"default:galeone"`   
    Age  int64  `gorm:"default:18"` 
}

查询记录

GORM 提供了 FirstTakeLast 方法,以便从数据库中检索单个对象。当查询数据库时它添加了 LIMIT 1 条件,且没有找到记录时,它会返回 ErrRecordNotFound 错误

// 获取第一条记录(主键升序) 
db.First(&user) 
// SELECT * FROM users ORDER BY id LIMIT 1;  

// 获取一条记录,没有指定排序字段 
db.Take(&user) 
// SELECT * FROM users LIMIT 1;  

// 获取最后一条记录(主键降序) 
db.Last(&user) 
// SELECT * FROM users ORDER BY id DESC LIMIT 1;  

result := db.First(&user) 
result.RowsAffected // 返回找到的记录数 
result.Error        // returns error or nil  

// 检查 ErrRecordNotFound 错误 
errors.Is(result.Error, gorm.ErrRecordNotFound) 

如果你想避免ErrRecordNotFound错误,你可以使用Find,比如db.Limit(1).Find(&user)Find方法可以接受struct和slice的数据。

如果主键是数字类型,我们可以使用内联条件来检索对象。 当使用字符串时,需要额外的注意来避免SQL注入。

db.First(&user, 10) // SELECT * FROM users WHERE id = 10;  

db.First(&user, "10") // SELECT * FROM users WHERE id = 10;  

db.Find(&users, []int{1,2,3}) // SELECT * FROM users WHERE id IN (1,2,3); 

如果主键是字符串(例如像uuid),查询将被写成如下:

db.First(&user, "id = ?", "1b74413f-f3b8-409f-ac47-e8c062e3472a") // SELECT * FROM users WHERE id = "1b74413f-f3b8-409f-ac47-e8c062e3472a"; 

检索全部对象

// 获得所有记录
result := db.Find(&users) 
// SELECT * FROM users;  

result.RowsAffected // 返回找到的记录数,等于“len(users)” 
result.Error        // 返回 error

String 条件

// 获得第一条匹配的记录
db.Where("name = ?", "jinzhu").First(&user) 
// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;  

// 获得所有匹配的记录
db.Where("name <> ?", "jinzhu").Find(&users) 
// SELECT * FROM users WHERE name <> 'jinzhu';  

// IN 
db.Where("name IN ?", []string{"jinzhu", "jinzhu 2"}).Find(&users) 
// SELECT * FROM users WHERE name IN ('jinzhu','jinzhu 2');  

// LIKE 
db.Where("name LIKE ?", "%jin%").Find(&users) 
// SELECT * FROM users WHERE name LIKE '%jin%';  

// AND 
db.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users) 
// SELECT * FROM users WHERE name = 'jinzhu' AND age >= 22;  

// Time 
db.Where("updated_at > ?", lastWeek).Find(&users) 
// SELECT * FROM users WHERE updated_at > '2000-01-01 00:00:00';  

// BETWEEN 
db.Where("created_at BETWEEN ? AND ?", lastWeek, today).Find(&users) 
// SELECT * FROM users WHERE created_at BETWEEN '2000-01-01 00:00:00' AND '2000-01-08 00:00:00'; 

Struct & Map 条件

// Struct 
db.Where(&User{Name: "jinzhu", Age: 20}).First(&user) 
// SELECT * FROM users WHERE name = "jinzhu" AND age = 20 ORDER BY id LIMIT 1;  

// Map 
db.Where(map[string]interface{}{"name": "jinzhu", "age": 20}).Find(&users) 
// SELECT * FROM users WHERE name = "jinzhu" AND age = 20;

// 主键切片
db.Where([]int64{20, 21, 22}).Find(&users) 
// SELECT * FROM users WHERE id IN (20, 21, 22);

注意 当使用结构体进行查询时,GORM 将仅使用非零字段进行查询,这意味着如果你的字段的值为 0、''、false 或其他零值,它将不会被用来构建查询条件,例如:

db.Where(&User{Name: "jinzhu", Age: 0}).Find(&users) // SELECT * FROM users WHERE name = "jinzhu";

要在查询条件中包含零值,可以使用map,它将包含所有键值作为查询条件,例如:

db.Where(map[string]interface{}{"Name": "jinzhu", "Age": 0}).Find(&users) // SELECT * FROM users WHERE name = "jinzhu" AND age = 0;

更新记录

更新所有列:Save 会保存所有的字段,即使字段是零值

db.First(&user) 

user.Name = "jinzhu 2" 
user.Age = 100 

db.Save(&user) // UPDATE users SET name='jinzhu 2', age=100, birthday='2016-01-01', updated_at = '2013-11-17 21:34:10' WHERE id=111; 

Save 是一个组合函数。 如果保存值不包含主键,它将执行 Create,否则它将执行 Update (包含所有字段)。

db.Save(&User{Name: "jinzhu", Age: 100}) // INSERT INTO `users` (`name`,`age`,`birthday`,`update_at`) VALUES ("jinzhu",100,"0000-00-00 00:00:00","0000-00-00 00:00:00")  

db.Save(&User{ID: 1, Name: "jinzhu", Age: 100}) // UPDATE `users` SET `name`="jinzhu",`age`=100,`birthday`="0000-00-00 00:00:00",`update_at`="0000-00-00 00:00:00" WHERE `id` = 1 

更新单个列

// 根据条件更新 
db.Model(&User{}).Where("active = ?", true).Update("name", "hello") 
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE active=true;  

// User 的 ID 是 `111` 
db.Model(&user).Update("name", "hello") 
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;  

// 根据条件和 model 的值进行更新 
db.Model(&user).Where("active = ?", true).Update("name", "hello") 
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111 AND active=true; 

更新多列:Updates 方法支持 struct 和 map[string]interface{} 参数。当使用 struct 更新时,默认情况下GORM 只会更新非零值的字段

// 根据 `struct` 更新属性,只会更新非零值的字段 
db.Model(&user).Updates(User{Name: "hello", Age: 18, Active: false}) 
// UPDATE users SET name='hello', age=18, updated_at = '2013-11-17 21:34:10' WHERE id = 111;  

// 根据 `map` 更新属性 
db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false}) 
// UPDATE users SET name='hello', age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111; 

更新选定字段:如果想要在更新时选择、忽略某些字段,我们可以使用 SelectOmit

// 选择 Map 的字段 
// User 的 ID 是 `111`: 
db.Model(&user).Select("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false}) 
// UPDATE users SET name='hello' WHERE id=111;  

db.Model(&user).Omit("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false}) 
// UPDATE users SET age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;

删除数据

删除一条记录:删除一条记录时,删除对象需要指定主键,否则会触发批量删除

// Email 的 ID 是 `10` 
db.Delete(&email) 
// DELETE from emails where id = 10;  

// 带额外条件的删除 
db.Where("name = ?", "jinzhu").Delete(&email) 
// DELETE from emails where id = 10 AND name = "jinzhu"; 

结语

通过本教程,我们学会了如何使用 GORM 连接数据库,并实现增删改查操作。这些知识不仅能够帮助我们简化数据库操作,还能提高代码的可读性和维护性。希望通过本文的介绍,你能够掌握 GORM 的基本用法,并在实际项目中得心应手地使用它。如果你有任何疑问或需要进一步的讨论,欢迎随时交流!