这是我参与「第五届青训营 」笔记创作活动的第 4 天
一、本堂课重点内容
- golang连接数据库的设计原理
- gorm框架的基本使用
二、详细知识点介绍
database/sql
1. 基本用法
我们可以通过引用各个数据库的实现包来进行对各种数据库的连接和调用,以下以mysql为例,进行一个简单的Demo示例(注意注释部分)。
package main
import "database/sql"
import _ "github.com/go-sql-driver/mysql"
type User struct {
Id string
Name string
}
func main() {
// import driver 实现(这里是mysql)
// 使用 driver + DSN 初始化DB连接
db, err := sql.Open("mysql", "root:password@tcp(127.0.0.1:3306)/hello")
// 执行sql
rows, err := db.Query("select id, name from user where id = ?", 1)
if err != nil {
return
}
if err != nil {
//TODO
// 异常处理
}
// 记得释放连接
defer func() {
err = rows.Close()
}()
// 数据处理
var userList []User
for rows.Next() {
var user User
err := rows.Scan(&user.Id, &user.Name)
if err != nil {
//TODO
// 异常处理
}
userList = append(userList, user)
}
// 处理错误
if rows.Err() != nil {
//TODO
// 异常处理
}
}
2. 设计原理
Golang 在设计时选择开发出一套简单的接口,供给各类数据库的开发者们进行实现,通过这样的方式,尽管各种数据库的实现、原理等等可能有些许不同,但使用Golang的程序员可以通过调用Golang在database/sql库中统一的函数,便可以轻松的操作不同的数据库
这里也介绍几种DB连接的类型
- 直接连接 / Conn
- 预编译 / Stmt
- 事务 / Tx
以及返回数据的几种方式:
- Exec / ExecContext -> Result
- Query / QueryContext -> Rows(Columns)
- QueryRow / QueryRowContext -> Row(Rows的简化)
Gorm 基础使用
背景知识
The fantastic ORM library for Golang aims to be developer friendly.
Gorm是一个设计简介、功能强大、自由扩展的全功能ORM
他的设计原则:
- API精简
- 测试优先
- 最小惊讶
- 灵活扩展
- 无依赖
- 可信赖
而且功能十分完善,有着许多优良特性:
- 全功能 ORM
- 关联 (Has One,Has Many,Belongs To,Many To Many,多态,单表继承)
- Create,Save,Update,Delete,Find 中钩子方法
- 支持
Preload、Joins的预加载 - 事务,嵌套事务,Save Point,Rollback To Saved Point
- Context、预编译模式、DryRun 模式
- 批量插入,FindInBatches,Find/Create with Map,使用 SQL 表达式、Context Valuer 进行 CRUD
- SQL 构建器,Upsert,数据库锁,Optimizer/Index/Comment Hint,命名参数,子查询
- 复合主键,索引,约束
- Auto Migration
- 自定义 Logger
- 灵活的可扩展插件 API:Database Resolver(多数据库,读写分离)、Prometheus…
- 每个特性都经过了测试的重重考验
- 开发者友好
基本用法
首先,当然需要引入依赖
import (
"gorm.io/gorm"
"gorm/io/driver/mysql"
)
其次便是连接数据库(这里以mysql为例)
db, err := gorm.Open(
mysql.Open("user:password@tcp(127.0.0.1:3306)/hello")
)
然后就是映射模型的建立
Model
模型的数据可以根据业务自行调整
模型的配置采用惯例约定的形式,
即 约定优于配置,gorm默认有以下规定:
- 表名为 struct name 的 蛇形命名(下划线)复数形式
- 字段名为 field name 的 蛇形命名(下划线单数形式
- ID/Id 字段为主键,如果为数字则默认为自增主键
- CreateAt 字段,创建时保存当前的时间
- UpdateAt 字段,创建、更新时保存当前的时间
- gorm.DeletedAt字段,默认开启逻辑删除
但是上述的一切都是可以进行配置的(其中第一条在刚学的时候折磨过我)
type User struct {
ID uint
Name string
}
接下来就是数据库与Golang接口体的映射了
开启映射
db.AutoMigrate(&Product{})
db.Migrator().CreateTable(&Product{})
接下来当然就是CRUD的接口了!
基本CRUD
Create
// 创建
user := User{Name: "Erhuo", Age: 18}
result := db.Create(&user)
// 批量创建
var users = []User{{Name: "Erhuo", Age: 18}, {Name: "Test", Age: 6}, {Name: "Absentia", Age: 153}}
db.Create(&users)
db.CreateInBatches(users, 100)
执行完Create指令后,传入数据以及返回结果:
- user.ID -> 插入时作为的主键
- result.Error -> 插入操作返回的错误
- result.RowsAffected -> 影响的行数
Read
// 读取
var product Project
db.First(&product, 1) // 查询id为1的product (根据主键查询)
db.First(&product, "code = ?", "114514") // 查询code为114514的product
// 批量查询
result := db.Find(&users, []int(1,2,3)) // 查询id分别为1,2,3的user (根据主键查询)
result.RowsAffected // 返回找到的记录数
errors.Is(result.Error, gorm.ErrRecordNotFound) // First, Last, Take 查不到数据
Update
// 更新某个字段
db.Model(&product).Update("price", 2000) // 根据主键将product的price修改为2000
db.Model(&product).UpdateColumn("price",2000) // 根据主键将product的price修改为2000
// 更新多个字段
db.Model(&product).Updates(Product{Price: 2000, Code: "114514"}) // 根据主键修改product的price和code
db.Model(&product).Updates(map[string]interface{}{Price: 2000, Code: "114514"}) // 根据主键修改product的price和code
// 批量更新
db.Model(&Product{}).Where("price < ?", 2000).Updates(map[string]interface{}["Price": 2000])
Delete
db.Delete(&product) // 根据主键删除
关联介绍
在关系型数据库中,必不会缺失的问题,就是如何处理表与表之间的关系
假设一个用户,他拥有一个账户(Account) 并且有多个宠物(Pets)等等复杂的关系,这时候我们该如何通过gorm来解决这样的问题呢?
gorm提供了这样的解决方式,详细请见下图代码
在进行多表操作时,我们也可以通过Preload或Joins指令来预加载或联表查询
当然对于复杂的关联关系,对于表数据的删除也需要小心(挨过坑),需要将依赖其的数据一同删除,这里可以采用gorm的级联删除
这里提供了三种方式:
- 数据库层面: 采用数据库约束自动删除
- GORM Migrate关联外键,并关闭逻辑删除
- 采用Select实现级联删除(不依赖数据库约束及软删除) (推荐!)