这是我参与「第三届青训营 -后端场」笔记创作活动的的第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)通过三条sqldb.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的实现,用自定义的方式与数据库交互 - 数据库方言,不同类型的数据库,要用不同的方式处理数据
- 在初始化连接时,可以指定连接的数据库类型