DATABASE/SQL 与 GORM 设计与实践
这是我参与「第三届青训营 -后端场」笔记创作活动的的第3篇笔记
概述
- Go语言中 Database/SQL 的实现,
- GORM 的实现原理、以及如何使用,甚至是如何基于 GORM 做一些定制化开发
基础知识支撑
-
什么是数据库、什么是 SQL
-
如何使用 database/sql 建立连接、使用
-
DSN 是什么
-
对 GORM 的简单认知
具体细节
1. 理解 Database/SQL
1.1 Database/SQL 的基本用法 --- Quick Start
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
func main(){//最原始写法
db, err := sql.Open("mysql","user:password@tcp(127.0.0.1:3306)/hello")
rows, err := db.Query("select id,name from users where id = ?",1)
if err != nil{
//xxx
}
defer func(){
err = rows.Close()
}
var users []User
for rows.Next(){
var user User
err := rows.Scan(&user.ID, &user.Name)
if err != nil{
//xxx
}
users = append(users,user)
}
if rows.Err() != nil {
//xxx
}
}
- 引入 database/sql 包
- database/sql 包只是提供了连接数据库的接口,具体怎么根据不同的数据库进行实现,需要数据库自己去设计不同的 driver,这里是引入了 mysql 的driver,然后通过 driver + DSN 初始化 DB 连接
- 执行一条 SQL ,通过rows 取回返回的数据,处理完毕需要释放链接
- 数据、错误处理,将获取到的数据封装到对应的实体对象中
- 错误处理!!!
- 【注意】defer 关闭连接时也要处理,否则会丢失错误信息
1.2 设计原理
-
向上提供操作接口,向下提供统一的连接接口与操作接口
-
池化技术,将获取复杂费事的资源池化,同时配套管理工具进行资源管理
(1)数据库连接池基础概念
(2)database/sql包执行SQL的伪代码实现
- 尝试获取连接并执行,总共3次
- 两种获取连接的策略:有空闲连接就拿来用(尽量复用),没有(前面的链接都出错了)就新建连接
- 使用完成后将连接放回连接池
- 当没有报错的时候,跳出循环,执行成功。
(3)【连接接口】database/sql 包如何预留接口让不同的 driver进行实现
- driver 接口方法 Open,传入DSN 返回一个连接,基于连接进行操作
- 函数 Register 注册全局 driver,把driver注册到全局变量 map 里
- driver 的使用: 在业务代码中 import driver,通过 main 方法建立连接,通过连接进行查询等
- 改良版:避免了过长的 DSN 以及重名问题 (推荐)
(4)【操作接口】
-
DB 连接的几种类型:
- 直接连接/ Conn
- 预编译/Stmt
- 事务/Tx
-
处理返回数据的几种方式:
- Exec / ExecContext -> Result(是否成功的信息)
- Query / QueryContext -> Rows (Columns)(以行形式返回查询到的信息)
- QueryRow / QueryRowContext -> Row (Rows 简化)
-
具体使用代码:
2. GORM基础使用
2.1 GORM基本用法
- 从数据库中取出user的数据,封装到User实体类中
import (
"gorm.io/gorm"
"gorm.io/driver/mysql"
)
func main(){
db, err := gorm.Open(
mysql.Open("user:password@tcp(127.0.0.1:3306)/hello")
)
var users []User
err = db.Select("id","name").Find(&users,1).Error
}
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
func main(){
db, err := sql.Open("mysql","user:password@tcp(127.0.0.1:3306)/hello")
rows, err := db.Query("select id,name from users where id = ?",1)
if err != nil{
//xxx
}
defer func(){
err = rows.Close()
}
var users []User
for rows.Next(){
var user User
err := rows.Scan(&user.ID, &user.Name)
if err != nil{
//xxx
}
users = append(users,user)
}
if rows.Err() != nil {
//xxx
}
}
- CRUD
2.2 Model 定义(实体类定义)
2.3 惯例约定(Model属性与表中字段如何对应)
2.4 关联操作
(1)GORM 支持很多关联形式
(2)CRUD
(3)Preload / Joins 预加载
- Preload:多级预加载,多条语句
- Joins:一条语句
(4)级联删除
3. GORM 的设计原理
Gorm 其实相当于在 database/sql 上又加了一层,用于优化用户与数据库之间的互动(类似 Java 中,Mybatis 对于 数据库和 JDBC 的连接)
3.1 SQL 生成的机制
3.2 插件扩展机制
(1)creat 的 callbacks
(2)插件使用示例
-
多租户系统
- 创建了一个新的 callback ,从statement里取出用户id,每次进行操作的时候就主动加上,以区分用户
-
所数据库、读写分离
3.3 ConnPool 扩展机制
- 像mybatis的多级缓存