初学GORM|青训营笔记

159 阅读5分钟

GORM简介

什么是ORM

在我们进行web开发时,不可避免的会使用到数据库软件来实现我们对数据的CRUD(增删改查),比如Mysql、Oracle等。其语法逻辑虽然相似,但都有细微差别。如果我们直接使用原生sql语句,会带来以下一些问题:

    1. 应用开发程序员需要耗费许多精力去优化sql语句
    1. 要上手新的数据库需要重新查询或记忆语法
    1. 如果需要进行数据库迁移(如从Mysql迁移到Oracle),则需要大幅度改动代码
    1. 原生sql语句会带来一些安全方面的问题。

为了解决上述缺点,ORM(Object Relational Mapping)应运而生。ORM在数据库软件之上再做了一层封装,将数据库抽象成了类。我们只需创建数据库对象后,指定driver,调用方法,ORM就会将对应的原生sql语句传递给后端数据库软件。这样,当我们要使用不同的数据库,只需要在原代码中修改driver即可。

eg.database/sql就是go官方提供的一个ORM

截屏2023-06-03 13.49.56.png

GORM概述

GORM是一个设计简洁、功能强大、自由扩展的全功能ORM。根据官方文档(gorm.io/docs/index.…),GORM的主要特点/功能有:

  • Associations (Has One, Has Many, Belongs To, Many To Many, Polymorphism, Single-table inheritance)
  • Hooks (Before/After Create/Save/Update/Delete/Find)
  • Eager loading with PreloadJoins
  • Transactions, Nested Transactions, Save Point, RollbackTo to Saved Point
  • Context, Prepared Statement Mode, DryRun Mode
  • Batch Insert, FindInBatches, Find/Create with Map, CRUD with SQL Expr and Context Valuer
  • SQL Builder, Upsert, Locking, Optimizer/Index/Comment Hints, Named Argument, SubQuery
  • Composite Primary Key, Indexes, Constraints
  • Auto Migrations
  • Logger
  • Extendable, flexible plugin API: Database Resolver (Multiple Databases, Read/Write Splitting) / Prometheus…
  • Every feature comes with tests
  • developer friendly

GORM基本用法

CRUD

1. 打开(Open)

gorm.open()方法打开一个数据库并返回数据库对象与error信息,第一个参数为driver,driver的Open方法需要传入数据库的DSN

import (
    "gorm.io/gorm"
    "gorm.io/driver/mysql"
)

func main(){
    db,err=gorm.open(mysql.Open("user:password@tcp(127.0.0.1:3306)/hello"),&gorm.Config{})
}

2. Create

创建单条记录:

user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}  
result := db.Create(&user) // pass pointer of data to Create  

user.ID             // returns inserted data's primary key 
result.Error        // returns error 
result.RowsAffected // returns inserted records count

创建多条记录:

users := []*User{   User{Name: "Jinzhu", Age: 18, Birthday: time.Now()},   User{Name: "Jackson", Age: 19, Birthday: time.Now()}, }
result := db.Create(users) // pass a slice to insert multiple row  

result.Error        // returns error 
result.RowsAffected // returns inserted records count

分批创建:

var users = []User{{Name: "jinzhu_1"}, ...., {Name: "jinzhu_10000"}}  
db.CreateInBatches(users, 100)// batch size 100 

3. Query

查询实例:

db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&orders) 
// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");
type User struct {   
    ID     uint   
    Name   string   
    Age    int   
    Gender string   
    // hundreds of fields 
}  
type APIUser struct {   
    ID   uint   
    Name string 
}
// Select `id`, `name` automatically when querying

db.Model(&User{}).Limit(10).Find(&APIUser{}) 
// SELECT `id`, `name` FROM `users` LIMIT 10

4. Update

更新实例:
db.Save(&User{Name: "jinzhu", Age: 100})
// INSERT INTO `users` (`name`,`age`,`birthday`,`update_at`) VALUES ("jinzhu",100,"0000-00-00 00:00:00","0000-00-00 00:00:00")  
db.Save(&User{ID: 1, Name: "jinzhu", Age: 100}) 
// UPDATE `users` SET `name`="jinzhu",`age`=100,`birthday`="0000-00-00 00:00:00",`update_at`="0000-00-00 00:00:00" WHERE

5. Delete

删除实例:

db.Delete(&User{}, 10) 
// DELETE FROM users WHERE id = 10;  
db.Delete(&User{}, "10") 
// DELETE FROM users WHERE id = 10;  
db.Delete(&users, []int{1,2,3}) 
// DELETE FROM users WHERE id IN (1,2,3); 

关联

1. belongs to

belongs to association sets up a one-to-one connection with another model, such that each instance of the declaring model “belongs to” one instance of the other model.

在下面的例子中,User属于且仅属于一个公司。

type User struct {   
    gorm.Model   
    Name      string  
    CompanyID int   
    Company   Company 
}  

type Company struct {
    ID   int   
    Name string 
}

2. has one

has one association sets up a one-to-one connection with another model, but with somewhat different semantics (and consequences). This association indicates that each instance of a model contains or possesses one instance of another model.

下面的例子中,User拥有一张CreditCard

type User struct {   
    gorm.Model   
    CreditCard CreditCard 
}  
type CreditCard struct {   
    gorm.Model   
    Number string   
    UserID uint 
}    

3. has many

has many association sets up a one-to-many connection with another model, unlike has one, the owner could have zero or many instances of models.

下面的例子中,User拥有多张CreditCard

type User struct {   
    gorm.Model   
    CreditCard []CreditCard 
}  
type CreditCard struct {   
    gorm.Model   
    Number string   
    UserID uint 
}    

4. many to many

Many to Many add a join table between two models.

下面的例子中User和Language的连接表为user_languages

type User struct {   
    gorm.Model   
    Languages []*Language `gorm:"many2many:user_languages;"` 
}  
type Language struct {   
    gorm.Model   
    Name string   
    Users []*User `gorm:"many2many:user_languages;"` 
}

5.预加载(Preload)

预加载可以在进行CRUD之前就将关系加载完毕,这样可以防止频繁地加载关系而形成许多raw sql语句,进而提升效率。

如下面的例子中,如果不使用Preload(),在每次Find()到user时都要加载一遍对应关系,使效率变低。

type User struct {
gorm.Model
Username string
Orders   []Order
}

type Order struct {
gorm.Model
UserID uint
Price  float64
}

// Preload Orders when find users
db.Preload("Orders").Find(&users)
// SELECT * FROM users;
// SELECT * FROM orders WHERE user_id IN (1,2,3,4);

GORM设计原理

1.生成raw sql语句

gorm将chain method不断拼接到gorm statement,当遇到finisher method后,将其类型选定并执行gorm statement

截屏2023-06-03 14.58.20.png

2.插件工作原理

截屏2023-06-03 15.06.04.png

一个create模式可以分成如下多段callback(创建事物,创建前,保存Before关系等等),create时会将callback依次调用(插件正在工作)。

Tips:总共有六种callback模式,大致流程与create模式相类似。

截屏2023-06-03 15.08.01.png

我们可以自己注册一些callback方法,并决定其插入位置。

截屏2023-06-03 15.18.19.png

最后,插件可以配置多数据库,进而实现读写分离等

截屏2023-06-03 15.25.43.png

3.ConnPool

ConnPool的主要作用就是预编译,这样用一样的sql语句就可以直接返回,而不需要再经过上面的编译过程了。在批量导入数据时,就可以开启预编译选项。具体使用可参考官方文档。

值得注意的还有mysql的interpolateParams参数,默认为false。其代表对所有使用的sql语句都预编译,但是使用完后就会被关闭,不会进入缓存。这样就让效率变得很低。这个参数的意义在于解决数据库的注入问题,但我们平时用的都是utf-8,并不会有注入问题。所以平时使用可以将其设为true,可以大大加速。 code.byted.org/gorm.bytedgorm包将许多参数调至更好状态,其中就包括了这个。在写代码时可以import并使用这个包。

4.dialector

dialector是一系列接口,可以定制sql生成、定制GORM插件、定制ConnPool等。类似的接口还有Option,直接修改配置文件或调用DB的任意api。