设计模式之database/sql与GORM设计实战 | 青训营笔记

342 阅读3分钟

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

理解database/sql

database/sql的基本用法

impart (
    "database/sql"
    "github.con/go-sql-driver/mysql"//import driver
)
func main() {
    db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/hello")//driver+DSN初始化db连接
    rows, err := db.Query("select id, name from users where id = ?", 1)//执行
    if err != nil {
        // xxx
    }
    defer ronws.Close()//释放连接

设计原理

image.png

GORM使用简介

基本用法

db, err := gorm.Open(mysql.Open("user:password@tcp(127.0.0.1:3306)/hello"))
var users []User
db.Create()//增
err = db.Select("id","name").Find(&users,1).Error //查
db.Model()//更新
db.delete()//删

Model 定义

type Model struct {
    ID uint `gorm:"primaryKey`
    CreatedAt time.time
    UpadateAt time.time
    Delete gorm.DeleteAt `gorm:"index"`
}

惯例约定

  • 表名为struct name的snake_cases复数格式
  • 字段名为field name的snake_case单数格式
  • ID/id字段为主键,如果为数字,则为自增主键
  • CreatedAt字段,创建时,保存当前时间
  • UpdatedAt字段,创建、更新时,保存当前时间
  • gorm.DeletedAt字段,默认开启soft delete模式

关联介绍

  • CRUD
langAssociation := db.Model(&user).Association("Languages")
langAssociation.Find(&languages)//查
langAssociation.Append()//增
langAssociation.Replace()//改
langAssociation.Deleter()//删
  • Preload
db.Preload()//预加载
db.Joins()//sql加载
  • 级联删除
//方法1:使用数据库约束自动删除
type User struct {
    ID uint
    Name string
    Account Account `garm: "constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
    CreditCards []CreditCard `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
    Orders []Order `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
}
// 需要使用GORM Migrate 数据库前一数据库外键才行
db.AutoMigrate(&User{})
//如果未启用软删除,在删除User时会自动删除其依赖
db.Delete(&User[])
//方法2:使用Select 实现级联删除,不依赖数据库约束及软删除
//删除user时,也删除user的Account记录
db.Select("Account" ).Delete(&user)
//删除user 时,也删除用户及其依赖的所有has one/many、 many2many 记录
db.Select(clause.Associations).Delete(&user)

GORM设计原理

使用GORM的核心思路梳理

一个对象 = 一行数据

示例中的一个User对象,完整地对应到具体users表中的一行数据,让整个框架更加清晰明了。每当数据库增加了一列,就对应地在结构体中加一个字段。这里有两个注意点:

  1. 不要在核心结构体User中加入非表中的数据,如一些计算的中间值,引起二义性;
  2. gorm.Model可以提升编码效率(会减少重复编码),但会限制数据库表中字段的定义,慎用(个人更希望它能开放成一个接口);

选择生效字段 = 核心结构体 + 字段数组

查询更新 接口里,我推荐的使用方法是采用核心结构体User+一个fields的数组,前者保存具体的数据、也实现了结构体复用,后者则选择生效的字段。

缩短链式调用

GORM的主要风格是链式调用,类似于Builder设计模式、串联堆起一个SQL语句。这种调用方式扩展性很强,但会带来了一个很严重的问题:容易写出一个超长的链式调用,可维护成本大幅度提高。

所以,在推荐使用方式里,区分了两种场景:

  1. 简单场景 - 核心结构体 + 字段数组
  2. 复杂场景 - 原生SQL

聚焦微服务的场景

作为一个ORM工具,GORM要考虑兼容各种SQL语句,内部非常庞大的。但如今更多地是考虑微服务的场景,这就能抛开大量的历史包袱,实现得更加简洁。这里我简单列举三个不太推荐使用的SQL特性:

  1. 减少group by - 考虑将聚合字段再单独放在一个表中
  2. 抛弃join - 多表关联采用多次查询(先查A表,然后用In语句去B表查)、或做一定的字段冗余(即同时放在A、B两个表里)
  3. 抛弃子查询,将相关逻辑放在代码里