设计模式之 Database/SQL 与 GORM 实践 | 青训营笔记

199 阅读3分钟

设计模式之 Database/SQL 与 GORM 实践 | 青训营笔记

这是我参与「第三届青训营 -后端场」笔记创作活动的的第5篇笔记

概述

本课程目标为带领同学们了解 Go 的 Database/SQL的实现,了解 GORM 的实现原理、使用简单,以及如何基于 GORM 做一些定制化开发。

课程结构为:

  • 理解 database/sql
  • GORM 使用简介
  • GORM 设计原理
  • GORM 最佳实践

主要内容

01. 理解 Database/SQL

1.1 Database/SQL 的基本用法

package quick_start
​
import (
    "database/sql"
)
​
type User struct {
    ID   int
    Name string
}
​
func main() {
    //使用driver + DSN 初始化DB连接
    db, err := sql.Open("mysql", "zzb:123456@tcp(127.0.0.1:3306)/hello ")
​
    //执行一条sql.通过rows取回返回的数据处理完毕,需要释放连接
    rows, err := db.Query("select id ,name from users where id = ?", 1)
    if err != nil {
        //XXX
    }
​
    //数据错误处理
    defer rows.Close() //可能会返回一个错误,需要进行检查
    defer func() {
        err = rows.Close()
    }()
​
    var users []User
    for rows.Next() {
        var user User
        rows.Scan(&user.ID, &user.Name)
        if err != nil {
​
        }
​
        users = append(users, user)
        //处理错误
        if rows.Err() != nil {
            //....
        }
    }
}

1.2 设计原理

image-20220515110833846

1.2.1 连接池配置
func(db *DB) SetConnMaxIdleTime(d time.Duration)
func(db *DB) SetConnMaxLifeTime(d time.Duration)
func(db *DB) SetConnMaxIdleConns(n int)
func(db *DB) SetConnMaxOpenConns(n int)
1.2.2 连接池状态
func (db *DB) Stats() DBStats
1.2.3 操作过程伪实现
func main() {
    for i := 0; i < maxBadConnRetries; i++ {
        //从连接池获取连接或通过 driver 新建连接
        dc, err := db.conn(ctx, strategy)
        //有空闲连接 -> reuse -> max life time
        //新建连接 -> max open...//将连接放回连接池
        defer dc.db.putConn(dc, err, true)
        //validateConnection 有无错误
        //max life time,max idle conns 检查//连接实现 friver.Queryer , driver,Execer 等 interface
        if err == nil {
            err = dc.ci.Query(sql, args...)
        }
        isBadConn = errors.Is(err, driver.ErrBadConn)
        if !isBadConn {
            break
        }
    }
}
1.2.4 Driver连接接口实现1
func main(){    
    db, err := sql.Open("mysql", "zzb:123456@tcp(127.0.0.1:3306)/hello ")
}
//注册Driver
func init(){
    sql.Register("mysql".&MySQLDriver{})
}
1.2.5 Driver连接接口实现2
func main() {
    connector, err := mysql.NewConnector(&mysql.Config{
        User:      "gorm",
        Passwd:    "gorm",
        Net:       "tcp",
        Addr:      "127.0.0.1:3306",
        DBName:    "gorm",
        ParseTime: true,
    })
​
    db := sql.OpenDB(connector)
}
1.2.6 操作接口

image-20220515114747283

02. GORM 的使用简介

2.1 背景知识

设计简洁、功能强大、只有扩展的全功能ORM

  • 设计原则: API 精简、测试优先、最小惊讶、灵活扩展、无依赖可信赖

  • 功能完善:

    • 关联:一对一、一对多、单表自关联、多态;Preload、 Joins 预加载、级联删除;关联模式;自定义关联表
    • 事务:事务代码块、嵌套事务、Save Point
    • 多数据库、读写分离、命名参数、Map、 子查询、分组条件、代码共享、SQL 表达式(查询、创建、更新)、自动选字段、查询优化器
    • 字段权限、软删除、批量数据处理、Prepared Stmt、自定义类型、命名策略、虚拟字段、自动track时间、SQL Builder、Logger
    • 代码生成、复合主键、Constraint、 Prometheus、 Auto Migration、真·跨数据库兼容…
    • 多模式灵活自由扩展
    • Developer Friendly

2.2 GORM 的基本用法

详见GROM文档gorm.io/docs/index.…

2.3 Model 定义

  • 惯例约定

    • 约定优于配置,配置也可以修改
    • 表名为struct namesnake_ cases 复数格式
    • 字段名为field namesnake_ case 单数格式
    • ID/ ld字段为主键,如果为数字,则为自增主键
    • CreatedAt字段,创建时,保存当前时间
    • UpdatedAt字段,创建、更新时,保存当前时间
    • gorm.DeletedAt字段,默认开启soft delete模式

2.4 关联介绍

  • Belongs To 属于
  • Has Many 一对多
  • Has One
  • Many To Many 多对多
  • 预加载
  • 级联删除

03. GORM 的设计原理

3.1 SQL 生成的机制

  • 自定义 Clause Builder

    • 支持不同版本的SQL
  • 方便扩展 Clause

  • 自由选择 Caluses

3.2 插件扩展机制

  • Fiisher Method -> 决定 Statement类型 -> 执行Callbacks -> 生成 SQL并执行

3.3 ConnPool 扩展机制

image-20220515123947528

  • 查找缓存的预编译SQL
  • 未找到,将收到的SQL和Vars预编译
  • 使用缓存的预编译SQL执行

开关:interpolateParams = false

3.4 Dialector 扩展机制

  • 定制SQL生成
  • 定制GORM插件
  • 定制ConnPool
  • 定制企业特定逻辑

04. GORM 最佳实践

4.1 GORM 最佳实践

  • 数据序列化与SQL表达式

    • SQL表达式查询
    • 数据序列化
  • 批量数据操作

    • 批量创建/查询
    • 批量更新
    • 批量数据加速操作
  • 代码复用、分库分表、sharding

  • 混沌工程/压测

  • Logger/Trace

  • Migrator

  • Gen代码生成/Raw SQL

  • 安全

4.2 定制企业级开发

4.3 FAQ & 工程成长