GORM连接数据库并进行CRUD | 青训营

97 阅读6分钟

0.GORM简介

ORM 指的是对象关系映射(Object-Relational Mapping),它是一种编程技术,用于在面向对象的编程语言(如Java、Python、Go等)和关系型数据库之间建立映射关系,从而允许开发者使用面向对象的方式来操作数据库,而不必直接编写原始的 SQL 查询语句。

需要注意的是,尽管ORM可以简化对数据库的操作,但也可能引入一些性能开销,某些复杂的数据库操作可能需要编写原生的SQL语句来优化性能。

不同编程语言有许多经典的 ORM 框架,如Java中的Hibernate,今天要学习的Gorm就是Go语言常用的ORM框架。以下是 Gorm 的一些特点和功能:

  1. 模型定义与映射: Gorm 允许你通过 Go 的结构体定义数据模型,并且提供了强大的结构体字段与数据库表字段之间的映射功能。你可以使用标签来定义字段的名称、类型、主键、索引等信息。
  2. CRUD 操作: Gorm 简化了对数据库的增删改查操作。你可以通过 Gorm 提供的方法轻松地执行创建、读取、更新和删除操作,而无需编写繁琐的 SQL 语句。
  3. 查询构建器: Gorm 提供了强大的查询构建器,允许你使用链式调用的方式构建复杂的查询语句。你可以执行条件查询、排序、分页等操作。
  4. 关联关系处理: Gorm 支持多种数据库关联关系,如一对一、一对多、多对多等。你可以在模型中定义关联关系,并使用预加载等功能来优化查询性能。
  5. 事务支持: Gorm 提供了事务管理功能,允许你在多个数据库操作中维护数据的一致性和完整性。
  6. 钩子(Hooks): Gorm 允许你在数据库操作的不同阶段插入自定义的逻辑,例如在保存前、保存后、删除前等。
  7. 迁移(Migration): Gorm 支持数据库迁移,可以让你在代码变更时保持数据库结构的同步。
  8. 跨数据库支持: Gorm 支持多种数据库后端,包括 MySQL、PostgreSQL、SQLite 等。
  9. 性能优化: Gorm 提供了一些性能优化选项,如批量插入、禁用日志等,以便更好地满足项目的需求。

1.连接数据库

在正式操作之前,需要先启用Go Module并安装GORM库、相应的驱动:

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

接下来,需要使用所选的数据库驱动来创建数据库连接。以笔者使用的MySQL为例:

func main() {
    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{
       PrepareStmt: true, //缓存预编译语句
    })
    if err != nil {
       panic(err)
    }
    // defer db.Close() 已弃用
}

db是一个指向gorm.DB的指针,使用db对象进行后续的数据库操作。

注意,在Gorm v1.20.0之后,db.Close方法被弃用,因为GORM支持连接池,所以正确的用法是打开一个连接并共享它。如果一定要关闭数据库连接可以考虑采用以下代码:

sqlDB, _ := db.DB()
defer sqlDB.Close()

2.定义模型

需要定义 Gorm 模型来映射数据库表格。模型通常是 Go 的结构体,使用 Gorm 的标签来定义表名、字段映射等信息。

type User struct {
    gorm.Model  // 内置的基础模型,包含 ID、CreatedAt、UpdatedAt、DeletedAt 字段
    Username string
    Email    string
}

在连接完毕并定义好与数据库表对应的模型后,可以使用 Gorm 的自动迁移功能来根据模型定义自动创建数据库表格:

func main() {
    // 上面是连接MySQL的相关代码
    err = db.AutoMigrate(&User{})
    if err != nil {
        panic("Failed to perform auto migration")
    }
}

创建结果如下:

image.png

3.创建记录

newUser := User{
    Username: "exampleuser1",
    Email:    "user1@qq.com",
}
result1 := db.Create(&newUser)
result2 := db.Create(&User{Username: "exampleuser2", Email: "user2@qq.com"})
if result1.Error != nil {
    panic("Failed to create record1")
}
if result2.Error != nil {
    panic("Failed to create record2")
}

执行完后,可以看到两条记录成功被创建:

image.png

4.查询记录

条件查询: 使用 Where 方法来根据查询条件查询记录。示例中的'='还可以换成'LIKE','IN'等

// 由于可能有多条记录,所以要用切片来接返回值
var users []User
db.Where("Username = ?", "user1").Find(&users)
println(len(users))

查询单个记录: 如果只想查询一条记录,可以使用 First 方法。

var user User
db.First(&user, "Username = ?", "user1")

排序和限制: 使用 Order 方法来指定排序规则,使用 Limit 方法来限制查询结果数量。

var users []User
result := db.Order("username desc").Limit(10).Find(&users) // 查询前10个记录,按用户名降序排列
if result.Error != nil {
    panic("Failed to retrieve users")
}

原始 SQL 查询:如果你需要执行更复杂的查询,你可以使用 Raw 方法执行原始 SQL 查询。

var users []User
result := db.Raw("SELECT * FROM users WHERE email LIKE ?", "%qq.com").Scan(&users)
if result.Error != nil {
    panic("Failed to retrieve users")
}

踩坑1:注意区分Find方法和First方法在未查询到记录时的返回结果

Find方法: 如果记录不存在不会报错,而是会返回一个空结构体,里面的值是结构体属性的零值 First方法:如果记录不存在会返回ErrRecordNotFonud错误,因此可以考虑Find和Limit方法结合来代替First方法

var user User
db.Where("Username = ?", "user1").LIMIT(1).Find(&user)

踩坑2:当使用结构作为条件查询时,GORM只会查询非零值字段,因此如果字段值为0、''、false或其他零值,该字段不会被用于构建查询条件,使用Map来构建查询条件可以解决此问题

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

5.更新记录

save方法:

var user User
db.First(&user, 1) // 查询ID为1的用户
user.Email = "new_email@example.com"
db.Save(&user) // 更新用户信息

Update方法(更新单列):

// 条件更新 
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;

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

// 根据条件和 model 的值进行更新 
db.Model(&user{ID: 111}).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; 

Update方法(更新多列),这里和查询多列的类似,既可以用struct也可以用map,但通过 struct 更新时,GORM 只会更新非零字段。 如果想确保指定字段被更新,你应该使用 Select 更新选定字段,或使用 map 来完成更新操作:

// 根据 `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, "actived": false}) 
// UPDATE users SET name='hello', age=18, actived=false, updated_at='2013-11-17 21:34:10' WHERE id=111; 

// Select 和 Struct (可以选中更新零值字段)
DB.Model(&result).Select("Name", "Age").Updates(User{Name: "new_name", Age: 0})
// UPDATE users SET name='new_name', age=0 WHERE id=111;

6.删除记录

删除单条记录:

var user User
db.First(&user, 1) // 查询ID为1的用户
db.Delete(&user)   // 删除用户记录

批量删除:

db.Where("email LIKE ?", "%jinzhu%").Delete(Email{}) 
// DELETE from emails where email LIKE "%jinzhu%";