本文同步发布在个人博客。
前言:何为ORM
要说ORM是何物,我们得先从面向对象谈起。
在面向对象的编程思想中贯彻着一句话:“一切皆对象。”
而在数据库那边,以关系型数据库来说吧,关系型数据库则讲究:“一切实体都有关系。”
你发现了什么?关系是不是也能用对象的思想去描述?
举个例子,假如有一张表:
CREATE TABLE `users` (
`id` integer PRIMARY KEY,
`username` varchar(255),
`role` varchar(255),
`created_at` timestamp
);
在这张名为users
的表内有着4个字段:id
,username
,role
和created_at
。
假如我们将它用Go的结构体去描述呢?
type Users struct {
Id int
Username string
Role string
CreatedAt time.Time
}
自此,我们便完成了一个从表到结构体的映射。
而ORM做的便是这样一种事情,从表映射到对象。ORM 就是通过实例对象的语法,完成关系型数据库的操作的技术,是"对象-关系映射"(Object/Relational Mapping) 的缩写。
一般来说,ORM会完成以下的映射关系:
- 数据库的表(table) --> 类(class)
- 记录(record,行数据)--> 对象(object)
- 字段(field)--> 对象的属性(attribute)
当然由于Go并没有class
这个概念,因此在Go中ORM会完成以下的映射关系:
- 数据库的表(table) --> 结构体(struct)
- 记录(record,行数据)--> 结构体的实例化(object)
- 字段(field)--> 结构体的字段(fields)
ORM有着下面的优点:
但也有一定的缺点:
Go 的ORM框架:GORM
在Go中也有着较为成熟的ORM框架:GORM,官网对它的特性简单枚举了一些:
- 全功能 ORM
- 关联 (拥有一个,拥有多个,属于,多对多,多态,单表继承)
- Create,Save,Update,Delete,Find 中钩子方法
- 支持 Preload、Joins 的预加载
- 事务,嵌套事务,Save Point,Rollback To to Saved Point
- Context、预编译模式、DryRun 模式
- 批量插入,FindInBatches,Find/Create with Map,使用 SQL 表达式、Context Valuer 进行 CRUD
- SQL 构建器,Upsert,锁,Optimizer/Index/Comment Hint,命名参数,子查询
- 复合主键,索引,约束
- 自动迁移
- 自定义 Logger
- 灵活的可扩展插件 API:Database Resolver(多数据库,读写分离)、Prometheus…
- 每个特性都经过了测试的重重考验
- 开发者友好
让我们结合一下MySQL简单上手一下GORM吧。
前期准备
由于笔者不喜欢物理机搞MySQL,所以此处使用Docker开一个MySQL的容器。
笔者已经安装好了Docker 和 MySQL 客户端,现在先拉取镜像。前往MySQL 的官方镜像:
在右侧已经写好了拉取命令,复制,在本地终端执行一下:
$ docker pull mysql
Using default tag: latest
latest: Pulling from library/mysql
49bb46380f8c: Pull complete
aab3066bbf8f: Pull complete
d6eef8c26cf9: Pull complete
0e908b1dcba2: Pull complete
480c3912a2fd: Pull complete
264c20cd4449: Pull complete
d7afa4443f21: Pull complete
d32c26cb271e: Pull complete
f1f84a2204cb: Pull complete
9a41fcc5b508: Pull complete
7b8402026abb: Pull complete
Digest: sha256:51c4dc55d3abf4517a5a652794d1f0adb2f2ed1d1bedc847d6132d91cdb2ebbf
Status: Downloaded newer image for mysql:latest
docker.io/library/mysql:latest
拉取完镜像后我们启动镜像:
$ docker run --name mysql -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql:latest
824cf9edeaaaf35aeaf58aed5a79c86fa819fd2693f063367b4a5a3404fa8aee
其中:
--name
是容器名字;-d
代表在后台运行;-p 3306:3306
代表将容器的3306端口映射到主机的3306端口;-e
是环境变量,这里有一个环境变量MYSQL_ROOT_PASSWORD
是指root用户的默认密码;mysql:latest
代表启动名为mysql
并且标签为latest
的镜像。
此时我们拿本地的MySQL客户端尝试一下:
$ mysql -uroot -p123456
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor. Commands end with ; or g.
Your MySQL connection id is 9
Server version: 8.0.34 MySQL Community Server - GPL
Copyright (c) 2000, 2023, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or 'h' for help. Type 'c' to clear the current input statement.
mysql>
成功连接。
为了后续的操作,我们在此建立一个test
的数据库:
mysql> create database test;
Query OK, 1 row affected (0.10 sec)
初始化Go项目
使用go get -u gorm.io/gorm
为项目导入GORM框架:
$ go get -u gorm.io/gorm
go: added github.com/jinzhu/inflection v1.0.0
go: added github.com/jinzhu/now v1.1.5
go: added gorm.io/gorm v1.25.2
初始化连接
由于我们使用的是MySQL,因此我们先要下载驱动:
$ go get -u "gorm.io/driver/mysql"
go: added github.com/go-sql-driver/mysql v1.7.1
go: added gorm.io/driver/mysql v1.5.1
下载完驱动后我们便可以连接数据库了,新建一个main.go
:
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
const (
user = "root"
password = "123456"
addr = "127.0.0.1:3306"
db = "test"
)
func main() {
dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?&parseTime=True&loc=Local",
user, password, addr, db)
//db 便是我们的数据库对象
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
fmt.Println("连接失败")
}
_ = db
}
可以看到,GORM 提供了gorm.Open
这个方法让我们去建立一个数据库的连接,而在建立连接的过程中我们也可以传递一些配置来配置连接,此处我们传入的是一个空结构体,因此我们没有传入任何配置。
func Open(dialector Dialector, opts ...Option) (db *DB, err error)
建立映射
前面我们已经说过了,ORM框架建立了记录——结构体的一个映射,因此我们此时就要先建立一个结构体。
例如这里我们新建一个user
的结构体:
type User struct {
gorm.Model
Name string
Age string
}
此处的gorm.Model
是框架自带的一个结构体,提供了常见的一些字段:
type Model struct {
ID uint `gorm:"primarykey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt DeletedAt `gorm:"index"`
}
标签
GORM框架提供了各种各样的标签来为结构体丰富自带的内容,所有的标签类型如下:
标签名 | 说明 |
---|---|
column | 指定表的列名 |
type | 列数据类型,推荐使用兼容性好的通用类型,例如:所有数据库都支持 bool、int、uint、float、string、time、bytes 并且可以和其他标签一起使用,例如:not null 、size , autoIncrement … 像 varbinary(8) 这样指定数据库数据类型也是支持的。在使用指定数据库数据类型时,它需要是完整的数据库数据类型,如:MEDIUMINT UNSIGNED not NULL AUTO_INCREMENT |
serializer | 指定将数据序列化或反序列化到数据库中的序列化器, 例如: serializer:json/gob/unixtime |
size | 定义列数据类型的大小或长度,例如 size: 256 |
primaryKey | 将列定义为主键 |
unique | 将列定义为唯一键 |
default | 定义列的默认值 |
precision | 指定列的精度 |
scale | 指定列大小 |
not null | 指定列为 NOT NULL |
autoIncrement | 指定列为自动增长 |
autoIncrementIncrement | 自动步长,控制连续记录之间的间隔 |
embedded | 嵌套字段 |
embeddedPrefix | 嵌入字段的列名前缀 |
autoCreateTime | 创建时追踪当前时间,对于 int 字段,它会追踪时间戳秒数,您可以使用 nano /milli 来追踪纳秒、毫秒时间戳,例如:autoCreateTime:nano |
autoUpdateTime | 创建/更新时追踪当前时间,对于 int 字段,它会追踪时间戳秒数,您可以使用 nano /milli 来追踪纳秒、毫秒时间戳,例如:autoUpdateTime:milli |
index | 根据参数创建索引,多个字段使用相同的名称则创建复合索引,查看 索引 获取详情 |
uniqueIndex | 与 index 相同,但创建的是唯一索引 |
check | 创建检查约束,例如 check:age > 13 ,查看 约束 获取详情 |
DESCRIBE users; +------------+-----------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +------------+-----------------+------+-----+---------+----------------+ | id | bigint unsigned | NO | PRI | NULL | auto_increment | | created_at | datetime(3) | YES | | NULL | | | updated_at | datetime(3) | YES | | NULL | | | deleted_at | datetime(3) | YES | MUL | NULL | | | name | longtext | YES | | NULL | | | age | longtext | YES | | NULL | | | nick_name | longtext | YES | | NULL | | +------------+-----------------+------+-----+---------+----------------+ 7 rows in set (0.00 sec) CRUD当我们建立好连接后就要开始增删改查了。 Create——增在GORM中,框架提供了
运行后我们此时查看表:
当然你也可以通过传入一个切片的方式来批量增加记录:
运行后查看原表:
Read——查GORM 提供了
WHERE在GORM中,也提供了和SQL类似的
Update——改当我们通过查询方法拿到记录后,我们可以更改这个结构体来更改记录,而后使用
DELETE——删首先确定两个概念: 为什么引入这两个概念,这里留给读者自行思考。 在GORM中也有着删除的方法:
注意的时,由于我们没有指定主键,因此GORM会删除一切符合筛选条件的记录。 如果我们根据主键删除:
软删除和硬删除GORM中,当你的结构体携带有
你也可以使用
总结GORM 作为Go 比较成熟的ORM 框架,它的业务能力是有目共睹的。对于新手而言,若要快速学习与SQL的交互,从GORM入手也许是一个不错的选择。 同时GORM还有着更多好玩的特性,下篇文章笔者将尝试讲解将Gin和Gorm结合起来的实际应用。
参考文档
|