这是我参与「第三届青训营 -后端场」笔记创作活动的的第6篇笔记
GO-学习笔记
database/sql
- 通过统一的接口,操作不一样的数据库
- 封装数据库连接接口和操作接口,对外暴露一样的操作接口
- 存在的问题
dsn data source name 数据源
连接url
是很长的字符串,存在转义的问题- 需要手动导入驱动
- 注册驱动时,发生重名,会发生运行时错误
- 优化的连接方式
connector, err := mysql.NewConnector(&mysql.Config{
User: "root",
Passwd: "qq978653881",
Net: "tcp",
Addr: "127.0.0.1:3306",
DBName: "local"})
db := sql.OpenDB(connector)
driver.Rows
,存储数据
使用
package main
import (
"database/sql"
"fmt"
// 需要导入驱动
_ "github.com/go-sql-driver/mysql"
)
type User struct {
name string
money int
}
func main() {
db, err := sql.Open("mysql", "root:qq978653881@tcp(127.0.0.1:3306)/local")
rows, err := db.Query("select name, money from bank where money = ?", 1500)
if err != nil {
//..
}
// 即时关闭,可能发生错误
defer rows.Close()
var users []User
for rows.Next() {
var user User
// 扫描,赋值给scan
err := rows.Scan(&user.name, &user.money)
if err != nil {
//..
}
users = append(users, user)
}
fmt.Println(users)
// 非数据相关的错误
if rows.Err() != nil {
}
}
连接池
- 封装了连接池
- 连接池配置
- 连接池状态
func (db *DB) Stats() DBStats
驱动接口
Driver
接口- 其实现方法
- 注册
driver
,放入drivers
- 在
init
时调用,在程序被执行,初始化包时调用
GORM
- ORM,Object Relational Mapping,对象关系映射,将数据库的数据映射为对象
- 中文文档
- 约定优于配置
- 表名为
struct
小写复数- 字段名为
field
小写ID/id
为int
类型,则为自增主键gorm.Model
包含ID/CreateAt/UpdateAt/DeletedAt
,可以直接嵌入
基本用法
type Bank struct {
Name string
Money int
}
func main() {
db, err := gorm.Open(mysql.Open("root:qq978653881@tcp(127.0.0.1:3306)/local"))
if err != nil {
//..
}
var banks []Bank
err = db.Select("name", "money").Find(&banks).Error
if err != nil {
fmt.Println(err)
return
}
fmt.Println(banks)
}
关联操作
- 针对嵌套结构的操作
- 预加载,提前加载对应的关联数据
db.Preload("Orders").Preload("Profile").Find(&banks)
通过三条sql
db.Joins("Orders").Joins("Profile").Find(&banks)
通过一条sql
,相当于左外连接查询
级联删除
- 通过外键约束
db.AutoMigrate(&bank)
db.Delete(&bank)
select
实现,删除bank
时,也删除order
中关联的内容
db.Select("Orders").Delete(&bank)
GORM设计原理
- 封装了
database/sql
sql生成
- 不同的
mysql
版本,sql
写法存在不同,gorm
抽取成统一的方法,通过事先确认数据源和驱动的版本,后续进行不同的sql
编译和拼接 - 分为
Chain Method
和Finisher Method
Find
为Finisher Method
,决定最终的类型,并进行执行
db.Where("age > ?", 35).Where("role <> ?", "manager").Order("age desc").Limit(100).Find(&employee)
where
添加子句find
执行,Query()
返回processor
结构体
- 将
find
操作支持的子句类型找出,并进行匹配- 编译成完整的
sql
- 交给
stmt 句柄
中的连接池执行
- 扩展子句
// 增加索引查询
db.Clauses(hints.UseIndex("idx_name")).Find(&user)
// 前中后拓展
db.Clauses(hints.Comment("select", "init_sql")).Find(&user)
db.Clauses(hints.CommentAfter("insert", "init_sql")).Find(&user)
db.Clauses(hints.CommentBefore("where", "init_sql")).Find(&user)
插件系统
- 灵活定制,自由扩展
- 多租户,不同客户数据需要隔离,查询时需要指定租户id,通过插件将租户id绑定通过插件到操作中,不用在业务设计中,关注租户id的条件
- 多数据库,读写分离,将读写数据库进行配置,在进行操作时,可以指定对哪个数据库进行操作
- 加解密,混沌工程
- 插件功能可以注册到某一种操作上
db.Callback().Create().Register("init_plugin", func(db *gorm.DB) { //...
})
create
,在创建时,依次调用下面的插件操作- 对插件的操作
- 读写分离配置
db.Use(dbresolver.Register(dbresolver.Config{
Sources: []gorm.Dialector{mysql.Open("db1")}, //主数据库
Replicas: []gorm.Dialector{mysql.Open("db2")}, //从数据库
Policy: dbresolver.RandomPolicy{}, //负载均衡
}).Register(dbresolver.Config{
Sources: []gorm.Dialector{mysql.Open("db3")},
Replicas: []gorm.Dialector{mysql.Open("db4")},
}, &user)) //针对users这个数据库,用这个配置
- 从
users
的主数据库中,读取数据
db.Clauses(dbresolver.Use(&user), dbresolver.Write).Find(&user)
ConnPool
gorm
通过ConnPool
真正去操作数据库- 读写操作连接池绑定不同的数据库,可以实现读写分离
PrepareStmt
会对所有的db
操作进行预编译和缓存,在后续不需要再次编译,只用加上参数
PrepareStmt
实现ConnPool
接口- 执行操作时,查询缓存的预编译
sql
,未找到进行编译interpolateParams=false
,默认为false
,防止参数注入,执行有参数的sql
,预编译,查询完之后进行关闭,用完就扔掉,不会缓存;设置为true
,参数直接插入,减少往返次数
- 全局模式
gorm.Open(mysql.Open("root:qq978653881@tcp(127.0.0.1:3306)/local"),
&gorm.Config{PrepareStmt: true})
- 会话模式
session := db.Session(&gorm.Session{PrepareStmt: true})
// 关闭
stmtManager := session.ConnPool.(*gorm.PreparedStmtDB)
stmtManager.Close()
mirrow
实现ConnPool
接口,可以定义存储数据库的过程,先存到某国数据库,再同步到企业数据库
Dialector
- 字节
bytedgorm
的实现,用自定义的方式与数据库交互 - 数据库方言,不同类型的数据库,要用不同的方式处理数据
- 在初始化连接时,可以指定连接的数据库类型