Go常用库-数据库GORM

写在前面:

GOrm版本: v1.9.11
GORM是Go上的数据库Lib,简单易用,而且是国人开发的,中文文档也很详尽.难得是其结构清楚,源码代码量并不大.(Java的Hibernate看得真是吐血).

Gorm官方快速入门代码

package main

import (
  "github.com/jinzhu/gorm"
  _ "github.com/jinzhu/gorm/dialects/sqlite"
)

type Product struct {
  gorm.Model
  Code string
  Price uint
}

func main() {
  db, err := gorm.Open("sqlite3", "test.db")
  if err != nil {
    panic("failed to connect database")
  }
  defer db.Close()

  // Migrate the schema
  db.AutoMigrate(&Product{})

  // 创建
  db.Create(&Product{Code: "L1212", Price: 1000})

  // 读取
  var product Product
  db.First(&product, 1) // 查询id为1的product
  db.First(&product, "code = ?", "L1212") // 查询code为l1212的product

  // 更新 - 更新product的price为2000
  db.Model(&product).Update("Price", 2000)

  // 删除 - 删除product
  db.Delete(&product)
}

从源码可以看出,使用顺序为:

  1. 定义Product结构(如果没有特殊设定,甚至可以不添加Gorm的tag).
  2. Gorm在打开Db后,就能通过AutoMigrate创建内部的ORM数据,并且在没有Products表(注意,默认表名会添加复数形式)情况下自定建表
  3. 使用&product进行普通的ORM增删改查操作.

一. Go核心库database/sql

1. Go的默认DB实现:

在Go的核心库中,定义关系型数据的基本操作和默认实现,这样各个数据库只需要实现符合要求的Driver接口实现通用.相比于JDBC标准只给了接口,具体实现都是各自去做,就少了C3P0,Druid,HikariCP等百花齐放的可能性.所以的GO数据库操作都由核心的DB统一进行实现,通过DB结构:

type DB struct {
    // Atomic access only. At top of struct to prevent mis-alignment
    // on 32-bit platforms. Of type time.Duration.
    waitDuration int64 // Total time waited for new connections.

    connector driver.Connector
    // numClosed is an atomic counter which represents a total number of
    // closed connections. Stmt.openStmt checks it before cleaning closed
    // connections in Stmt.css.
    numClosed uint64

    mu           sync.Mutex // protects following fields
    freeConn     []*driverConn
    connRequests map[uint64]chan connRequest
    nextRequest  uint64 // Next key to use in connRequests.
    numOpen      int    // number of opened and pending open connections
    // Used to signal the need for new connections
    // a goroutine running connectionOpener() reads on this chan and
    // maybeOpenNewConnections sends on the chan (one send per needed connection)
    // It is closed during db.Close(). The close tells the connectionOpener
    // goroutine to exit.
    openerCh          chan struct{}
    resetterCh        chan *driverConn
    closed            bool
    dep               map[finalCloser]depSet
    lastPut           map[*driverConn]string // stacktrace of last conn's put; debug only
    maxIdle           int                    // zero means defaultMaxIdleConns; negative means 0
    maxOpen           int                    // <= 0 means unlimited
    maxLifetime       time.Duration          // maximum amount of time a connection may be reused
    cleanerCh         chan struct{}
    waitCount         int64 // Total number of connections waited for.
    maxIdleClosed     int64 // Total number of connections closed due to idle.
    maxLifetimeClosed int64 // Total number of connections closed due to max free limit.

    stop func() // stop cancels the connection opener and the session resetter.
}

它确实也通过freeConn管理暂时空闲的连接,通过connRequests管理正在与数据库通讯的chan等机制实现了一个高效的数据库连接池,并且对于使用者屏蔽了多个数据库连接间的竞争,对于使用来说,可以使用唯一的全局DB变量操作数据库.

2. database/sql源码

driver.go

这个里面基本都是接口,Go定义了驱动必须实现的一些接口和方法,如果Connector就有Connect()方法,Execer就有Exec(),当DB需要实际连接数据库时候,就是掉用这些接口和数据库实际交互.

types.go

主要定义了ValueConverter和valuer接口,表示go的数据结构bool,int32,String怎么与数据库字段间进行转换,并且实现了一个默认的转换器DefaultParameterConverter,其ConvertValue()方法就是使用反射进行赋值.

convert.go

主要定义了Scan查询结果时候的转换方法,其中默认就用了driver里面定义的DefaultParameterConverter.

sql.go

这个是整个lib的核心,大约有3000行,包装了所有的数据库操作,查询执行SQL,事务控制,查询结果Result处理,Stml参数等等.
值得注意的是,这里对每个链接都使用ctx管理上下文,保证在调用Db.Close()是能通过 <-ctx.Done()方法传导到所有的连接上,使得连接能正常关闭.

3. database/sql文档参考

http://go-database-sql.org/overview.html(官方教程)

https://yq.aliyun.com/articles/178898

https://www.jianshu.com/p/5e7477649423 (翻译的一般)

https://segmentfault.com/a/1190000003036452 (精简版)

https://studygolang.com/articles/3022 (还有性能分析-good)

http://cngolib.com/database-sql.html (库source-中文说明)

https://studygolang.com/articles/14685 (有流程图-good)

二. GORM源码阅读

GORM基于原生的DB进行扩展,增加对面向对象程序员更加友好的ORM机制.程序员只需要定义数据结构,由框架提供标准的方法就能进行数据操作.从官方文档也看出Create,Delete确实方便.查询就相对麻烦点,需要通过Where(),Not(),Or()等进行条件的整合(这点Hibernate的JPA确实强大,通常只需要写方法名即可)

其实源码阅读,分析处理机制的的文档已经很多了,这边简单记录一下自己的阅读体会就好.大致上,GORM主要完成了2部分工作:
一. 增强了DB的能力,包括定义Dialect对数据库特性的检测,Callback在运行创建/查询/更新/删除语句之前或者之后执行需要的函数;
二. 通过把使用者定义的Stuct结构图解析成ModelStruct,形成结构体和数据表,结构体域和数据列的映射,并通过上面提到的钩子完成ORM操作(所谓的ORM把查询结果映射到Object也就是通过queryCallback钩子在调用query执行后,自动执行scan把查询结果Field中)

1. GORM增强DB能力:

dialect.go

dialect包装了不同数据库不同执行方式,它是一个接口,包括GetName(),HasIndex(),HasForeignKey(),HasTable()等方法, dialect_mysql.go, dialect_sqlite3.go就是具体实现.
例如: HasTable()方法,mysql查询的是Tables_in_{currentDatabase}表,而sqllite查询的是sqlite_master表.
其中定义了包变量dialectsMap,把已经有的dialect按照driverName作为Key进行存储,这样在newDB的时候,就能取出对应的dailect.

callback.go

结构如下:

type Callback struct {
    logger     logger
    creates    []*func(scope *Scope)
    updates    []*func(scope *Scope)
    deletes    []*func(scope *Scope)
    queries    []*func(scope *Scope)
    rowQueries []*func(scope *Scope)
    processors []*CallbackProcessor
}

可以看出这个结构存储了各种钩子,在create,update,delete,query时候就可以进行回调.其中processors结构为:

// CallbackProcessor contains callback informations
type CallbackProcessor struct {
    logger    logger
    name      string              // current callback's name
    before    string              // register current callback before a callback
    after     string              // register current callback after a callback
    replace   bool                // replace callbacks with same name
    remove    bool                // delete callbacks with same name
    kind      string              // callback type: create, update, delete, query, row_query
    processor *func(scope *Scope) // callback handler
    parent    *Callback
}

登记了所有的钩子信息,这样在sortProcessors()方法就能根据其kind放到特定的数组中去.
另外,特定回调的名称是固定的,如BeforeSave()和BeforeCreate()就会在beforeCreateCallback中执行.这里scope.go的callMethod()里面根据注册的不同函数签名,执行不同的方法.

2. GORM实现ORM:

model_struct.go

StructField代表了GORM对结构体域的理解,通过反射机制读取tag,并解析到TagSettings中,从而获得该结构域对应于数据库列的信息,IsPrimaryKey表示是否为主键,HasDefaultValue表示是否有默认值等.
而ModelStruct就代表了GORM对数据实体的解析.

// ModelStruct model definition
type ModelStruct struct {
    PrimaryFields []*StructField
    StructFields  []*StructField
    ModelType     reflect.Type

    defaultTableName string
    l                sync.Mutex
}

其中GetModelStruct()方法是AutoMigrate是解析的方法.

field.go

Field是个组合结构,包括了StructField结构以及该域的值

search.go

search是一个容器结构,记录了当前需要和数据库交互的各种查询条件

type search struct {
    db               *DB
    whereConditions  []map[string]interface{}
    orConditions     []map[string]interface{}
    notConditions    []map[string]interface{}
    havingConditions []map[string]interface{}
    joinConditions   []map[string]interface{}
    initAttrs        []interface{}
    assignAttrs      []interface{}
    selects          map[string]interface{}
    omits            []string
    orders           []interface{}
    preload          []searchPreload
    offset           interface{}
    limit            interface{}
    group            string
    tableName        string
    raw              bool
    Unscoped         bool
    ignoreOrderQuery bool
}

我们在使用GORM的DB.Where()或者DB.OR时,都把条件先整合到这个Search中.

scope.go

scope是整个GORM的核心结构了,相当于Gin里面的Context.它存储了一次DB操作所有上下文,包括查询实体,查询条件,查询结果(已查询为例)

// Scope contain current operation's information when you perform any operation on the database
type Scope struct {
    Search          *search
    Value           interface{}
    SQL             string
    SQLVars         []interface{}
    db              *DB
    instanceID      string
    primaryKeyField *Field
    skipLeft        bool
    fields          *[]*Field
    selectAttrs     *[]string
}

核心方法

  • Fields() : 从GetModelStruct()中获取StructFields信息,然后逐个遍历,组合成Field(包括类型信息和值)
  • Exec() : 实际是调用了scope.SQLDB().Exec(),并把结果记录到RowsAffected
  • Begin(), CommitOrRollback(): 进行事务处理的封装
  • scan(): 处理SQL执行结果sql.Rows,遍历所有的列,利用反射对每个结果field赋值
  • buildCondition(): 把Search里面的各种条件,组合成SQL语句
  • related(): 处理表间关系(略,暂时未看)
  • createJoinTable(): 连接表(略,暂时未看)
  • createTable(),dropTable(),autoIndex(): 自动创建Table时的动作.

3. main-功能统一封装:

main.go
// DB contains information for current db connection
type DB struct {
    sync.RWMutex
    Value        interface{}
    Error        error
    RowsAffected int64

    // single db
    db                SQLCommon
    blockGlobalUpdate bool
    logMode           logModeValue
    logger            logger
    search            *search
    values            sync.Map

    // global db
    parent        *DB
    callbacks     *Callback
    dialect       Dialect
    singularTable bool

    // function to be used to override the creating of a new timestamp
    nowFuncOverride func() time.Time
}

DB结构是GORM的操作结构,从上面key看出,

  • 他登记了核心库db注意,这里只声明SQLCommon接口,不包括TX操作,因此在Tx相关代码里面还有进行类型转换,当然带来的好处是,把Tx变成了可选的实现).
  • 记录了当前还未执行的search信息,
  • parent,这个是全局的,New的时候没有,但是在Open的时候db的parent指向了自身,这样在创建Scope是执行Db.clone()创建出的Scope中用的db,都指向了全局的父亲db(感觉这样直接指向自己有点怪,应该改clone为derived,然后parent是赋值向调用者的)
  • callbacks,钩子也是全局唯一的.clone时候并没赋值这个.
  • dialect, SQLDB特性实现,根据DriverName查找的.

DB结构的主要方法都是创建一个新的Scope,然后委托他和DB进行交互.

4. GORM文档参考

http://gorm.io/zh_CN (官网)
https://juejin.im/post/5d81a07ef265da03dc07a4ac
https://jiajunhuang.com/articles/2019_03_19-gorm.md.html
https://zhuanlan.zhihu.com/p/42480289
https://studygolang.com/articles/20361
https://cloud.tencent.com/developer/article/1558764
https://cloud.tencent.com/developer/article/1558766

帅气猫咪的系列文章-重点阅读:
https://juejin.im/post/5dff0787518825125015f824
https://juejin.im/post/5e081077f265da33f718c9d2
https://juejin.im/post/5e1d91876fb9a030094bca3a

GO夜读81 - gorm 介绍与展望
https://www.bilibili.com/video/BV1pE411N7Sv

你可能感兴趣的:(Go常用库-数据库GORM)