GORM简介
什么是ORM
在我们进行web开发时,不可避免的会使用到数据库软件来实现我们对数据的CRUD(增删改查),比如Mysql、Oracle等。其语法逻辑虽然相似,但都有细微差别。如果我们直接使用原生sql语句,会带来以下一些问题:
-
- 应用开发程序员需要耗费许多精力去优化sql语句
-
- 要上手新的数据库需要重新查询或记忆语法
-
- 如果需要进行数据库迁移(如从Mysql迁移到Oracle),则需要大幅度改动代码
-
- 原生sql语句会带来一些安全方面的问题。
为了解决上述缺点,ORM(Object Relational Mapping)应运而生。ORM在数据库软件之上再做了一层封装,将数据库抽象成了类。我们只需创建数据库对象后,指定driver,调用方法,ORM就会将对应的原生sql语句传递给后端数据库软件。这样,当我们要使用不同的数据库,只需要在原代码中修改driver即可。
eg.database/sql就是go官方提供的一个ORM
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
Preload,Joins - 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
A 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
A 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
A 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
2.插件工作原理
一个create模式可以分成如下多段callback(创建事物,创建前,保存Before关系等等),create时会将callback依次调用(插件正在工作)。
Tips:总共有六种callback模式,大致流程与create模式相类似。
我们可以自己注册一些callback方法,并决定其插入位置。
最后,插件可以配置多数据库,进而实现读写分离等
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。