这是我参与「第五届青训营 」伴学笔记创作活动的第 12 天
对象关系映射 (Object Relational Mapping)
简介
面对对象编程是由软件工程基本原则发展而来,数据库是由数学理论发展而来,两者理论具有显著差异。
为了屏蔽这个差异,让开发者能像操作对象一样操作数据库,对象关系映射技术应运而生。
目标
像操作对象一样操作数据库。
要素
- 建立数据库连接
- 建立、配置映射对象
- 增删改查
- 开启事务
- 用对象方式描述表间一对一、一对多、多对多关系
Gorm
介绍
迭代了十多年的ORM框架
建立数据库连接
API
基于 Dialector接口初始化一个数据库 session
func Open(dialector Dialector, opts ...Option) (db *DB, err error)
而所有的数据库厂商都会根据接口做自己的实现。
比如 Mysql 提供的驱动包的 Open 方法,给出连接词作为参数,就会返回 Dialector 实例。
这样 gorm 就可以利用返回的 Dialector 实例初始化一个数据库 session。
func Open(dsn string) gorm.Dialector
示例
db, err := gorm.Open(
mysql.Open("user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=local")
, &gorm.Config{})
建立、配置映射对象
示例
type User struct {
Id int64 `gorm:"column:id"`
Name string `gorm:"column:name"`
Avatar string `gorm:"column:avatar"`
Level int `gorm:"column:level"`
CreateTime time.Time `gorm:"column:create_time"`
ModifyTime time.Time `gorm:"column:modify_time"`
}
func (User) TableName() string {
return "user"
}
其中Model 定义 遵守以下原则
思想:约定大于配置
- 表名为 struct name 的 snake_cases 复数形式,比如 struct user 结构体,对应表明users
- 字段名为 field name 的 snake_case 单数形式
- ID / id 对应主键,为数字的话,就是自增主键
- CreateAt字段,创建时,保存当前时间。
- UpdateAt字段,创建\更新时,保存当前时间
- gorm.DeleteAt字段,delete 模式。
增删改查
插入数据
API
func (db *DB) Create(value interface{}) (tx *DB)
示例
user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}
result := db.Create(&user) // 通过数据的指针来往数据库插入数据
查询
API
查找按主键排序、匹配给定条件的第一条记录
// 查找按主键排序、匹配给定条件的第一条记录
func (db *DB) First(dest interface{}, conds ...interface{}) (tx *DB)
查找按主键排序、匹配给定条件的最后一条记录
// 查找按主键排序、匹配给定条件的最后一条记录
func (db *DB) Last(dest interface{}, conds ...interface{}) (tx *DB)
返回本次操作影响记录数,定义在 DB 结构体中
type DB struct {
*Config
Error error
RowsAffected int64
Statement *Statement
// contains filtered or unexported fields
}
returns error or nil,定义在结构体中
type DB struct {
*Config
Error error
RowsAffected int64
Statement *Statement
// contains filtered or unexported fields
}
示例
// 获取第一条记录(主键升序)
db.First(&user)
// SELECT * FROM users ORDER BY id LIMIT 1;
// 获取一条记录,没有指定排序字段
db.Take(&user)
// SELECT * FROM users LIMIT 1;
// 获取最后一条记录(主键降序)
db.Last(&user)
// SELECT * FROM users ORDER BY id DESC LIMIT 1;
result := db.First(&user)
result.RowsAffected // 返回找到的记录数
result.Error // returns error or nil
删除
API
删除匹配给定条件的值。
如果 value 包含主键,则包含在条件中。
如果 value 包含 deleted_at 字段,则执行软删除,将 deleted_at 设置为当前时间(如果为空)。
func (db *DB) Delete(value interface{}, conds ...interface{}) (tx *DB)
示例
删除一条记录
// Email 的 ID 是 `10`
db.Delete(&email)
// DELETE from emails where id = 10;
// 带额外条件的删除
db.Where("name = ?", "jinzhu").Delete(&email)
// DELETE from emails where id = 10 AND name = "jinzhu";
根据主键删除
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);
批量删除
db.Where("email LIKE ?", "%jinzhu%").Delete(&Email{})
// DELETE from emails where email LIKE "%jinzhu%";
db.Delete(&Email{}, "email LIKE ?", "%jinzhu%")
// DELETE from emails where email LIKE "%jinzhu%";
更新
API
指定你想要运行 db 操作的 Model
func (db *DB) Model(value interface{}) (tx *DB)
update 使用回调函数更新列
func (db *DB) Update(column string, value interface{}) (tx *DB)
where 往语句里添加条件
func (db *DB) Where(query interface{}, args ...interface{}) (tx *DB)
示例
// update all users's name to `hello`
// 更新所有 user 的 name 属性为hello
db.Model(&User{}).Update("name", "hello")
//如果 user 的主键非空,则将其拼接到数据库sql中,然后只将用户名更新为' hello '
db.Model(&user).Update("name", "hello")
更新单个列
// 条件更新
db.Model(&User{}).Where("active = ?", true).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE active=true;
// User 的 ID 是 `111`
db.Model(&user).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;
// 根据条件和 model 的值进行更新
db.Model(&user).Where("active = ?", true).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111 AND active=true;
更新多列
// 根据 `struct` 更新属性,只会更新非零值的字段
db.Model(&user).Updates(User{Name: "hello", Age: 18, Active: false})
// UPDATE users SET name='hello', age=18, updated_at = '2013-11-17 21:34:10' WHERE id = 111;
// 根据 `map` 更新属性
db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET name='hello', age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;
更新选定字段
// Select with Map
// User's ID is `111`:
db.Model(&user).Select("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET name='hello' WHERE id=111;
db.Model(&user).Omit("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;
// Select with Struct (select zero value fields)
db.Model(&user).Select("Name", "Age").Updates(User{Name: "new_name", Age: 0})
// UPDATE users SET name='new_name', age=0 WHERE id=111;
// Select all fields (select all fields include zero value fields)
db.Model(&user).Select("*").Updates(User{Name: "jinzhu", Role: "admin", Age: 0})
// Select all fields but omit Role (select all fields include zero value fields)
db.Model(&user).Select("*").Omit("Role").Updates(User{Name: "jinzhu", Role: "admin", Age: 0})
事务
API
开启事务
func (db *DB) Begin(opts ...*sql.TxOptions) *DB
提交在一次事务中的更改。
func (db *DB) Commit() *DB
撤销一次事务中的更改。
func (db *DB) Rollback() *DB
fc 函数块中开启事务,成功则提交,失败则回退。
func (db *DB) Transaction(fc func(tx *DB) error, opts ...*sql.TxOptions) (err error)
示例
手动事务
// 开始事务
tx := db.Begin()
// 在事务中执行一些 db 操作(从这里开始,您应该使用 'tx' 而不是 'db')
tx.Create(...)
// ...
// 遇到错误时回滚事务
tx.Rollback()
// 否则,提交事务
tx.Commit()
自动事务
db.Transaction(func(tx *gorm.DB) error {
// 在事务中执行一些 db 操作(从这里开始,您应该使用 'tx' 而不是 'db')
if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
// 返回任何错误都会回滚事务
return err
}
if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {
return err
}
// 返回 nil 提交事务
return nil
})
组合示例
// model 定义
type Product struct {
Code string
Price uint
}
func (p Product) TableName() string {
return "product"
}
func main() {
// 连接数据库
db, err := gorm.Open(
mysql.Open("user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=local")
, &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
// 添加记录
// Create
db.Create(&Product{Code: "D42", Price: 100})
// 查询数据
// Read
var product Product
db.First(&product, 1) // 根据整形主键查找
db.First(&product, "code = ?", "D42") // 查找 code 字段值为 D42 的记录
// 更新数据
// Update - 将 product 的 price 更新为 200
db.Model(&product).Update("Price", 200)
// Update - 更新多个字段
db.Model(&product).Updates(Product{Price: 200, Code: "F42"}) // 仅更新非零值字段
db.Model(&product).Updates(map[string]interface{}{"Price": 200, "Code": "F42"})
// 删除数据
// Delete - 删除 product
db.Delete(&product, 1)
}
其中Model 定义 遵守以下原则
思想:约定大于配置
- 表名为 struct name 的 snake_cases 复数形式,比如 struct user 结构体,对应表明users
- 字段名为 field name 的 snake_case 单数形式
- ID / id 对应主键,为数字的话,就是自增主键
- CreateAt字段,创建时,保存当前时间。
- UpdateAt字段,创建\更新时,保存当前时间
- gorm.DeleteAt字段,delete 模式。
用对象方式描述表间一对一、一对多、多对多关系
支持的数据库
- Mysql
- SQLServer
- PostgreSQL
- SQLite GORM 通过驱动来连接数据库,如果需要连接其他类型的数据库,可以复用/自行开发驱动。
GORM Hook
GORM 性能优化
GORM 生态
database/sql 包
简介
Golang标准库中为关系型数据库提供统一接口的包。
简单使用
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
func mian() {
// 连接数据库
db, err := sql.Open("mysql", "user:password@/tcp(127.0.0.1:3306)/dbname")
// 执行查询语句,保存到rows
rows, err := db.Query("select id, name from users where id = ?", 1)
if err != nil {
// xxx
}
defer rows.CLose()
// 遍历查询结果,映射到User结构体中
var users []user
for rows.Next() {
var user User
err := rows.Scan(&user.ID, &user.Name)
if err != nil {
// ...
}
users = append(users, user)
}
if rows.Err() != nil {
// ...
}
}
// 连接池配置
func (db *DB) SetConnMaxIdLeTime(d time.Duration)
func (db *DB) SetConnMaxLifeTime(d time.Duration)
func (db *DB) SetMaxidleConn(n int)
func (db *DB) SetMaxOpenConns(n int)
// 连接池状态
func (db *DB) Status() DBStats
// 注意事项
必须使用 defer rows.Close() 关闭连接
虽然游标遍历完成后,会自动关闭连接,但是如果中间出现问题(错误返回、panic、回滚),就会导致数据库连接泄露。
rows.Close() 关闭连接的过程也会发生错误,需要去检查
必须处理rows.Err(),返回不是数据相关的错误。
设计原则
极简接口设计原则
- 对上层应用提供简单的操作接口。
- 用户可以通过统一API操作数据。
- 为下层数据库提供简单的驱动接口。
- 数据库提供商通过实现统一接口,就可以将自己的数据库引入支持。
- data/sql 自身实现连接池。
- 实现了连接的复用,连接池的配置、管理。
常见接口