GORM框架 | 青训营笔记

208 阅读8分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 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 自身实现连接池。
    • 实现了连接的复用,连接池的配置、管理。

常见接口

image.png image.png