GORM 指南 | GORM - The fantastic ORM library for Golang, aims to be developer friendly.
什么是ORM
ORM是Object Relational Mapping的缩写,译为“对象关系映射”,它解决了对象和关系型数据库之间的数据交互问题。
- Gorm 是 Go 语言中实现对象和数据库映射的框架,可以有效地提高开发数据库应用的效率。
- Gorm 主要用途是把 struct类型和数据库表 进行映射,使用简单方便。因此,使用 Gorm 操作数据库的时候不需要直接手写 SQL 语句代码。
和自动生成SQL语句相比,手动编写SQL语句的缺点是非常明显的,主要体现在以下两个方面:
- 对象的属性名和数据表的字段名往往不一致,我们在编写SQL语句时需要非常小心,要逐一核对属性名和字段名,确保它们不会出错,而且彼此之间要——对应。
- 此外,当SQL语句出错时,数据库的提示信息往往也不精准,这给排错带来了不小的困难
- 不同的数据库,对应的sq|语句也不太一样
- sql注入问题
当然,使用orm也不全是优点,ORM缺点
- ORM增加了大家的学习成本,为了使用ORM技术,您至少需要掌握一种ORM框架。
- 自送生成SQL语句会消耗计算资源,这势必会对程序性能造成一定的影响。
- 对于复杂的数据库操作,ORM通常难以处理,即使能处理,自动生成的SQL语句在性能方面也不如手写的原生SQL。
- 生成SQL语句的过程是自动进行的,不能人工干预,这使得开发人员无法定制一些特殊的SQL语句。
每一门语言都有对应的ORM框架
- python: SQLAlchemy DjangoORM
- Java: Hibernate Mybatis
- Golang:GORM
GORM入门
每个语言提供的orm是一个作用,就是将你的对象和数据库表建立映射关系。(比如一张表在go里面对应的可能是一个结构体,在对数据库做增删改查的时候是对这些结构体进行操作)
使用golang的orm框架就是将golang当中的struct,就是结构体和数据库当中的表字段进行一个映射。也就是操作结构体不是直接操作数据库了,而是通过结构体了。当然这里要使用数据库相关的sdk了。
总而言之就是操作结构体就能够操作数据库,操作结构体,那么表里面的数据其实也就会跟着发生变化。
orm框架主要的作用就是,在go里面就是struct,在java里面其实就是class类,也即是让对象和表产生一个映射关系,我们就不需要操作表了,直接操作对象就行了。
什么是GORM?
参考文档: https://gorm.io/zh_CN/docs/index.html GORM是一个神奇的,对开发人员友好的 Golang ORM 库
- 全特性 ORM (几乎包含所有特性)
- 模型关联 (一对一, 一对多,一对多 (反向), 多对多, 多态关联)
- 钩子 (Before/After Create/Save/Update/Delete/Find)
- 预加载
- 事务
- 复合主键
- SQL 构造器
- 自动迁移
- 日志
- 基于GORM回调编写可扩展插件
- 全特性测试覆盖
- 开发者友好
1.3 GORM(v2)基本使用
1. 安装
go get -u gorm.io/gorm
2. 连接MySQL
先创建一个数据库
mysql> create database test_db charset utf8; # 创建数据库
mysql> use test_db; # 切换到数据库
mysql> show tables; # 查看是否生成表
+-------------------+
| Tables_in_test_db |
+-------------------+
| users |
+-------------------+
mysql> desc users; # 查看表的字段是否正常
+----------+------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+------------+------+-----+---------+----------------+
| id | bigint(20) | NO | PRI | NULL | auto_increment |
| username | longtext | YES | | NULL | |
| password | longtext | YES | | NULL | |
+----------+------------+------+-----+---------+----------------+
创建 mysql 连接 ,参考文档: https://gorm.io/zh_CN/docs/connecting_to_the_database.html
Gin链接和使用MySQL数据库
安装MySQL驱动想要在Gin中操作MySQL,首先要有MySQL驱动程序。该驱动程序需要下载:
需要下载mysql的驱动,如果项目依赖包不使用 go mod 管理,那么就需要使用 go get 命令安装以上依赖包:
go get gorm.io/driver/mysql
go get gorm.io/gorm
如果导入的包是不使用的,那么在前面加上_,比如
import _"gorm.io/driver/mysql"
不使用这个包为什么还要导入呢?当我导入进来不使用的时候,说明包里面init函数是要执行的,其实就是执行里面的init去做mysql驱动的初始化。
parseTime解析时间:gorm里面自带一些创建时间,更新时间,删除时间。在做映射关系的时候拿到的时候是一个时间对象,这里就会自动转化为时间对象。
loc=Local:用本地环境的一个时区,也就是使用本地时间
timeout:="10s" //连接超时,10秒
dsn就是拼接的一些信息,root:7PXjAkY!&nlR@tcp(192.168.11.128:3306)/test?后面是可以加上一些参数。
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
var (
username = "root"
password = "sMo!G(LbU13E11"
host = "192.168.87.128"
port = 3306
dbname = "test"
timeout = "10s"
DB *gorm.DB
)
func init() {
dns := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local&timeout=%s",
username, password, host, port, dbname, timeout)
//连接MySQL,获取DB类型实例,用于后面数据库读写操作
db, err := gorm.Open(mysql.Open(dns), &gorm.Config{})
if err != nil {
panic(err)
}
DB = db
}
type User struct {
ID int64 `gorm:"primary_key"`
Username string
Password string
}
func main() {
//连接成功
fmt.Println(DB)
}
&{0xc00011a5a0 <nil> 0 0xc00021e000 1}
这个db其实就决定了要操作的是哪一个库,表使用结构体去区分,或者使用表名区分都是可以的。如果要去打开多个库,那么多写几个open就行了,你每次open的时候都是生成了db实例,db实例后面是不同的库,多初始化几个db实例就行了。
dsn := "root:7PXjAkY!&nlR@tcp(192.168.11.128:3306)/test_db?charset=utf8mb4&parseTime=True&loc=Local"
//这是固定写法
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
高级配置(对连接当中的配置做一些处理)
- 跳过默认事务 一般都是打开的,这里只做演示
为了确保数据一致性,GORM会在事务里执行写入操作(创建、更新、删除)。如果没有这方面的要求,您可以在初始化时禁用它,这样可以获得60%的性能提升(对于增删改查都会创建大的事务,如果没有这方面的需求可以禁用掉)
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
SkipDefaultTransaction: true,
})
命名策略 约定
GORM 倾向于约定优于配置,默认情况下,GORM 使用 ID
作为主键,使用结构体名的蛇形复数
作为表名,字段名的蛇形
作为列名,并使用 CreatedAt
、UpdatedAt
字段追踪创建、更新时间
如果您遵循 GORM 的约定,您就可以少写的配置、代码。 如果约定不符合您的实际要求,GORM 允许你配置它们
gorm采用的命名策略是,表名是蛇形复数,字段名是蛇形单数例如
type Student struct {
Name string
Age int
MyStudent string
}
type User struct {} // 默认的表名是 `users`
gorm会为我们这样生成表结构,默认表结构为小写字母+末尾复数s
CREATE TABLE 'students'('name' longtext,'age' bigint,'my_student' longtext)
注意这里传递的是结构体指针类型,而不是结构体类型
type Student struct {
ID uint
StudentName string
Age int
}
DB.AutoMigrate(&Student{})
mysql> show tables;
+----------------+
| Tables_in_test |
+----------------+
| credit_cards |
| languages |
| product |
| students |
| user_infos |
| users |
+----------------+
mysql> desc students;
+--------------+-----------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+-----------------+------+-----+---------+----------------+
| id | bigint unsigned | NO | PRI | NULL | auto_increment |
| student_name | longtext | YES | | NULL | |
| age | bigint | YES | | NULL | |
+--------------+-----------------+------+-----+---------+----------------+
我们也可以修改这些默认的命名策略,这些只是做演示,不建议修改默认配置。
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
TablePrefix: "f_", //表名前缀,都加上f_
SingularTable: true, //单数表名
NoLowerCase: true, //不要小写转换
},
SkipDefaultTransaction: true,
})
mysql> show tables;
+----------------+
| Tables_in_test |
+----------------+
| credit_cards |
| f_Student |
| languages |
| product |
| user_infos |
| users |
+----------------+
mysql> desc f_Student;
+-------------+-----------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+-----------------+------+-----+---------+----------------+
| ID | bigint unsigned | NO | PRI | NULL | auto_increment |
| StudentName | longtext | YES | | NULL | |
| Age | bigint | YES | | NULL | |
+-------------+-----------------+------+-----+---------+----------------+
自动创建表
参考文档:https://gorm.io/zh_CN/docs/models.html
默认生成的表是结构体小写名字加上复数即users)
primary_key是gorm的标签。
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type User struct {
ID int64 `gorm:"primary_key"`
Username string
Password string
}
func main() {
dsn := "root:7PXjAkY!&nlR@tcp(192.168.11.128:3306)/test_db?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
fmt.Println(err)
}
//自动迁移
err = db.AutoMigrate(User{})
if err != nil {
fmt.Println(err)
}
}
mysql> show tables;
+-------------------+
| Tables_in_test_db |
+-------------------+
| users |
+-------------------+
1 row in set (0.00 sec)
mysql> desc users;
+----------+----------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+----------+------+-----+---------+----------------+
| id | bigint | NO | PRI | NULL | auto_increment |
| username | longtext | YES | | NULL | |
| password | longtext | YES | | NULL | |
+----------+----------+------+-----+---------+----------------+
3 rows in set (0.00 sec)
自定义表名
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type User struct {
Id int64 `gorm:"primary_key"`
Username string
Password string
}
func (*User) TableName() string {
return "user"
}
func main() {
dsn := "root:7PXjAkY!&nlR@tcp(192.168.11.128:3306)/test?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
} else {
fmt.Println(db)
db.AutoMigrate(&User{})
}
}
之后对于表的操作都是对user这张表进行操作
type TablerWithNamer interface {
TableName(Namer) string
}
其实就是去重写了这个方法,其实也是接口的这个逻辑,在AutoMigrate的时候,被调用了,基于这个表名,去创建了这张表。
gorm.Model
GORM 定义一个 gorm.Model
结构体,其包括字段 ID
、CreatedAt
、UpdatedAt
、DeletedAt
// gorm.Model 的定义
type Model struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
}
对于匿名字段,GORM 会将其字段包含在父结构体中,例如:
type User struct {
gorm.Model
Name string
}
// 等效于
type User struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
Name string
}
创建记录
user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}
result := db.Create(&user) // 通过数据的指针来创建
user.ID // 返回插入数据的主键
result.Error // 返回 error
result.RowsAffected // 返回插入记录的条数
我们还可以使用 Create()
创建多项记录:
users := []*User{
User{Name: "Jinzhu", Age: 18, Birthday: time.Now()},
User{Name: "Jackson", Age: 19, Birthday: time.Now()},
}
result := db.Create(users) // pass a slice to insert multiple row
result.Error // returns error
result.RowsAffected // returns inserted records count
基本增删改查
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
//自定义返回表名
func (* User) TableName() string {
return "user"
}
// User 表的结构体ORM映射
type User struct {
Id int64 `gorm:"primary_key"`
Username string
Password string
}
func main() {
// 0、连接数据库
dsn := "root:1@tcp(127.0.0.1:3306)/test_db?
charset=utf8mb4&parseTime=True&loc=Local"
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
db.AutoMigrate(User{})
// 1、增
db.Create(&User{
//Id: 3,
Username: "zhangsan",
Password: "123456",
})
[31.540ms] [rows:1] INSERT INTO `users` (`username`,`password`,`created_at`,`updated_at`,`deleted_at`) VALUES ('lucas','123456','2023-06-12 17:19:
34.776','2023-06-12 17:19:34.776',NULL)
// 2、改
db.Model(User{Id: 3,}).Update("username", "lisi")
//db.Model(User{}).Where("id = 3").Update("username", "lisi")
[4.154ms] [rows:1] UPDATE `users` SET `username`='lisa',`updated_at`='2023-06-12 17:23:27.574' WHERE `users`.`deleted_at` IS NULL AND `id` = 1
// 3、查
// 3.1 过滤查询
u := User{Id: 3}
db.First(&u)
fmt.Println(u)
// 3.2 查询所有数据
users := []User{}
db.Find(&users)
fmt.Println(users) // [{2 zhangsan 123456} {3 lisi 123456}]
[0.518ms] [rows:4] SELECT * FROM `users` WHERE `users`.`deleted_at` IS NULL
// 4、删
// 4.1 删除 id = 3 的用户
db.Delete(&User{Id: 3})
// 4.2 条件删除
db.Where("username = ?", "zhangsan").Delete(&User{})
}
修改数据其实就是指定你的结构体,也就是你的表是哪个。model在修改数据的时候封装了where条件的功能,update就是修改某个字段。如果加上debug那么整条语句执行转化为sql打印出来。(model主要两个功能 1 找到对应的表 2 填入条件字段)(无论在做数据库的什么操作,你都需要有一个地方去指定这个映射关系让其找到这张表)
db.Debug().Model(&User{Id: 1}).Update("username", "lisi")
[1.638ms] [rows:0] UPDATE `user` SET `username`='lisi' WHERE `id` = 1
或者使用下面这种方法,这里table就直接指定你的表名了,而model是指定你的结构体,这里是有映射关系的。
db.Debug().Table("user").Where("id=?", 1).Update("username", "wan")
[3.300ms] [rows:1] UPDATE `user` SET `username`='wan' WHERE id=1
上面所有使用到结构体的都和那张表有关系。
查询某条数据使用first 查询唯一值
u := &User{Id: 1}
db.Debug().First(u)
fmt.Println(u)
db.Where("id=?",u.Id).First(u)
查询所有的数据
var users []User
db.Find(&users)
fmt.Println(users)
删除
db.Where("username =? and id =?","wangwu",1).Delete(&User{})
官方网站 快速入门示例:
package main
import (
"gorm.io/gorm"
"gorm.io/driver/sqlite"
)
type Product struct {
gorm.Model
Code string
Price uint
}
func main() {
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
// 迁移 schema
db.AutoMigrate(&Product{})
// Create
db.Create(&Product{Code: "D42", Price: 100})
// Read
var product Product
db.First(&product, 1) // 根据整型主键查找
db.First(&product, "code = ?", "D42") // 查找 code 字段值为 D42 的记录
// Update - 将 product 的 price 更新为 200
db.Model(&product).Update("Price", 200)
// Update - 更新多个字段
db.Model(&product).Updates(Product{Price: 200, Code: "F42"}) // 仅更新非零值字段
db.Model(&product).Updates(map[string]interface{}{"Price": 200, "Code": "F42"})
// Delete - 删除 product
db.Delete(&product, 1)
}