从 ORM 到 GORM 简介 | 青训营

149 阅读6分钟

什么是 ORM

ORM 是 Object Relation Mapping 缩写,译为“对象关系映射”,它是一种为了解决面向对象编程与关系型数据库之间互不匹配的问题的技术。

ORM 通过描述对象和数据库之间的映射的元数据,将程序中的对象自动地持久化到关系型数据库中。简单来说,就是使用一个类表示一张表,类中的属性表示表的字段,类的实例化对象表示一条记录,通过使用对象的方法来操纵数据库的增、删、改、查的操作

ORM 的宗旨是将数据库的操作抽象化,以面向对象的方式来操作数据库,从而使得开发人员可以更加关注于业务逻辑的实现,而不用过多地关注底层的数据库操作。ORM 框架具有很多特性,比如:自动建表、自动映射、自动增删改查等等。相比手动编写 SQL,ORM 具有提高开发效率,降低代码量,提高代码可读性,提高代码可维护性、可重用性和可移植性等等优点。

ORM 缺点

当然,ORM 也不全是优点。首先,ORM 增加了开发中的学习成本,为了使用 ORM 技术,开发者必须熟练掌握一种 ORM 框架。

其次,自动生成 SQL 语句会消耗计算资源,势必会对程序性能造成一定的影响,这是 ORM 的硬伤。然而,随着计算机硬件的不断迭代,这部分造成的影响逐渐显得不那么重要了。

再次,对于复杂的数据库操作,ORM 通常难以处理,即使能处理,自动生成的 SQL 语句在性能方面也不如手写的原生 SQL。

最后,生成 SQL 语句必须是自动进行的,不能人工干预。这意味着,开发人员无法定制一些特殊的 SQL 语句。

目前主流的编程语言几乎都配备了相应的 ORM 框架:

  • python: SQLAIchemy, DjangoORM
  • Java: Hibernate, Mybatis
  • Golang: GORM (本文主要介绍这个!)

GORM 官方文档

GORM 官方是有中文文档的,可以在这里看到。它介绍了 GORM 的使用流程:包括如何安装、如何定义模型、如何连接到数据库以及 CRUD 接口。本文挑其中的基础知识做简单介绍,连接的数据库选择 MySQL。

模型定义

模型定义其实就是数据库中的建表过程。在关系型数据库中,所有记录都表示成若干个字段的值的组合。这很自然地让人想到将其映射为 Go 中的结构体以暂存在内存中。在 GORM 中,模型是标准的 struct,由 Go 的基本数据类型、实现了 Scanner 和 Valuer 接口的自定义类型及其指针或别名组成。

type User struct {
  ID           uint
  Name         string
  Email        *string
  Age          uint8
  Birthday     *time.Time
  MemberNumber sql.NullString
  ActivatedAt  sql.NullTime
  CreatedAt    time.Time
  UpdatedAt    time.Time
}

GORM 倾向于约定优于配置 默认情况下,GORM 使用 ID 作为主键,使用结构体名的 蛇形复数 作为表名,字段名的 蛇形 作为列名,并使用 CreatedAt、UpdatedAt 字段追踪创建、更新时间。所谓 蛇形(snake_case) 就是指每个空格皆以底线(_)取代的书写风格。如果您遵循 GORM 的约定,您就可以少写配置、代码。

连接数据库

数据库连接直接调用 gorm 的 Open() 方法即可。

db, err := gorm.Open("mysql", DSN)

这里的 DSN 是一个字符串,全称是 Data Source Name,译为数据源名称,用来描述数据库连接信息。一般都包含数据库连接地址,账号,密码之类的信息。DSN 的格式定义如下。

[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...&paramN=valueN]

CRUD 接口

CRUD 即对数据库的增(Create)、查(Research)、改(Update)、删(Delete)的操作。在 GORM 中,这些操作通过对指定类型的接口函数的调用来实现。

增 Create

官方推荐的数据创建方式是为 Create 函数传入结构体指针或者结构体指针的切片,这不仅可以创建单条记录而且可以创建多条记录。按照约定,GORM 会在数据库中查找与结构体名称相符合的表,然后将数据插入进去并将新增的数据的值赋给结构体,这通常很有用,譬如我们想要获得自动生成的主码。

// 插入单条记录
user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}
result := db.Create(&user)

// 插入多条记录
users := []*User{
    User{Name: "Jinzhu", Age: 18, Birthday: time.Now()},
    User{Name: "Jackson", Age: 19, Birthday: time.Now()},
}
result := db.Create(users)

查 Research

在 MySQL 中,查询依赖于 SELECT,WHERE,ORDER BY,LIMIT 等关键字。如果说 GORM 的表名约定实现了 FROM 关键字,那么 GORM 的 First(), Take(), Last(), Find(), Where(), Order() 等接口函数就实现了 SELECT,WHERE,ORDER BY,LIMIT 等关键字。以下是最为常见的用法。

// SELECT * FROM users ORDER BY id LIMIT 1
db.First(&user)

// SELECT * FROM users LIMIT 1
db.Take(&user)

// SELECT * FROM users ORDER BY id DESC LIMIT 1
db.Last(&user)

// SELECT * FROM users
db.Find(&user)

// SELECT * FROM users WHRER <CONDITION>
db.Where(<CONDITION>).Find(&user)

其中,Where() 接受的参数 <CONDITION> 可以是 String 条件,也可以是 Struct&Map 条件,还可以指定查询字段,设置内联条件和 AND/NOT/OR 条件组合。当然,GORM 还提供 Group() 和 Join() 函数提供表的分组和拼接操作,可以看官方文档里的详细说明。

改 Update

更新包括两种方法,一种是 Save(),另一种是 Update()。Save 会保存所有的字段,即使字段是零值,Save() 是一种复合方法,它还可以用于插入数据。而 Update 可以更新单个列或者多个列。

// Save() 未指定主键,是增加
db.Save(&User{Name: "jinzhu", Age: 100})

// Save() 指定了主键,是更新
db.Save(&User{ID: 1, Name: "jinzhu", Age: 100})

// Update() 更新单列,需要用 db.Model() 指定表
db.Model(&User{}).Where("active = ?", true).Update("name", "hello")

// Update() 更新多列,需要用 db.Model() 指定表
db.Model(&user).Updates(User{Name: "hello", Age: 18, Active: false})

删 Delete

删除一条记录时,删除对象需要指定主键,否则会触发批量删除。如果指定的值不包括主属性,那么 GORM 会执行批量删除,它将删除所有匹配的记录

// 指定主键删除
db.Delete(&User{Id: 10})

// 批量删除
db.Where("email LIKE ?", "%jinzhu%").Delete(&Email{})
db.Delete(&Email{}, "email LIKE ?", "%jinzhu%") // 与上等价

软删除:如果你的模型包含了 gorm.DeletedAt字段(该字段也被包含在gorm.Model中),那么该模型将会自动获得软删除的能力!

总结

ORM 解决了面向对象编程和关系型数据库之间互不匹配的问题,是一种对数据库底层操作的抽象化办法。它通过将 SQL 的语句生成任务交由计算机自动进行,对数据进行了良好的映射,方便开发者编写高可读性、高可维护性、高可重用性和高可移植性的代码。GORM 遵循约定优于配置,将关系型数据库的表映射成结构体,将 CRUD 操作映射成接口函数,具有对开发者的友好性。