4 GROM 教程 (golang)

在这里插入图片描述

链式操作

链式操作

Method Chaining,Gorm 实现了链式操作接口,所以你可以把代码写成这样:

db, err := gorm.Open("postgres", "user=gorm dbname=gorm sslmode=disable")

// 创建一个查询
tx := db.Where("name = ?", "jinzhu")

// 添加更多条件
if someCondition {
  tx = tx.Where("age = ?", 20)
} else {
  tx = tx.Where("age = ?", 30)
}

if yetAnotherCondition {
  tx = tx.Where("active = ?", 1)
}

在调用立即执行方法前不会生成 Query 语句,有时候这会很有用。

比如你可以抽取一个函数来处理一些通用逻辑。

立即执行方法

Immediate methods ,立即执行方法是指那些会立即生成 SQL 语句并发送到数据库的方法, 他们一般是 CRUD 方法,比如:

Create, First, Find, Take, Save, UpdateXXX, Delete, Scan, Row, Rows

这有一个基于上面链式方法代码的立即执行方法的例子:

tx.Find(&user)

生成的 Sql

SELECT * FROM users where name = 'jinzhu' AND age = 30 AND active = 1;

范围

Scopes,Scope 是建立在链式操作的基础之上的。

基于它,你可以抽取一些通用逻辑,写出更多可重用的函数库。

func AmountGreaterThan1000(db *gorm.DB) *gorm.DB {
  return db.Where("amount > ?", 1000)
}

func PaidWithCreditCard(db *gorm.DB) *gorm.DB {
  return db.Where("pay_mode_sign = ?", "C")
}

func PaidWithCod(db *gorm.DB) *gorm.DB {
  return db.Where("pay_mode_sign = ?", "C")
}

func OrderStatus(status []string) func (db *gorm.DB) *gorm.DB {
  return func (db *gorm.DB) *gorm.DB {
    return db.Scopes(AmountGreaterThan1000).Where("status IN (?)", status)
  }
}

db.Scopes(AmountGreaterThan1000, PaidWithCreditCard).Find(&orders)
// 查找所有金额大于 1000 的信用卡订单

db.Scopes(AmountGreaterThan1000, PaidWithCod).Find(&orders)
// 查找所有金额大于 1000 的 COD 订单

db.Scopes(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Find(&orders)
// 查找所有金额大于 1000 且已付款或者已发货的订单

多个立即执行方法

Multiple Immediate Methods,在 GORM 中使用多个立即执行方法时,后一个立即执行方法会复用前一个 立即执行方法的条件 (不包括内联条件) 。

db.Where("name LIKE ?", "jinzhu%").Find(&users, "id IN (?)", []int{1, 2, 3}).Count(&count)

生成的 Sql

SELECT * FROM users WHERE name LIKE 'jinzhu%' AND id IN (1, 2, 3)

SELECT count(*) FROM users WHERE name LIKE 'jinzhu%'

线程安全

所有链式方法都会创建并克隆一个新的 DB 对象 (共享一个连接池),GORM 在多 goroutine 中是并发安全的。

错误处理

Go的错误处理是很重要的

建议您在调用任何 立即执行方法 后进行错误检查

错误处理

GORM中的错误处理与惯用的Go代码不同,因为它具有可链接的API,但仍然易于实现。

如果发生任何错误,GORM将设置* gorm.DB的错误字段,可以这样检查:

if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil {
  // error 处理...
}

或者

if result := db.Where("name = ?", "jinzhu").First(&user); result.Error != nil {
  // error 处理...
}

错误

处理数据时,通常会发生多个错误。 GORM提供了一个API来将所有错误作为切片返回:

// 如果发生了一个以上的错误, `GetErrors` 以`[]error`形式返回他们
errors := db.First(&user).Limit(10).Find(&users).GetErrors()

fmt.Println(len(errors))

for _, err := range errors {
  fmt.Println(err)
}

记录未找到错误

RecordNotFound,GORM提供了处理 RecordNotFound 错误的快捷方式。如果有多个错误,它将逐一检查这些错误是否为 RecordNotFound 错误。

// 检查是否为 RecordNotFound 错误
db.Where("name = ?", "hello world").First(&user).RecordNotFound()

if db.Model(&user).Related(&credit_card).RecordNotFound() {
  // 未找到记录
}

if err := db.Where("name = ?", "jinzhu").First(&user).Error; gorm.IsRecordNotFoundError(err) {
  // 未找到记录
}

钩子

对象生命周期

Hooks(一般称之为钩子函数)的功能是在运行创建/查询/更新/删除语句之前或者之后执行。

如果你为一个 model 定义了一个具体的方法,它将会在运行 创建,更新,查询,删除时自动被调用,并且如果任何回调函数函数返回一个错误,GORM 将会停止接下来的操作并且回滚当前的事务。

钩子函数

创建对象时

Creating an object,创建对象时可用的 hooks

// 开始事务
BeforeSave
BeforeCreate
// 在关联前保存
// 更新时间戳 `CreatedAt`, `UpdatedAt`
// save self
// 重新加载具有默认值的字段,其值为空
// 在关联后保存
AfterCreate
AfterSave
// 提交或回滚事务

示例代码

func (u *User) BeforeSave() (err error) {
  if !u.IsValid() {
    err = errors.New("can't save invalid data")
  }
  return
}

func (u *User) AfterCreate(scope *gorm.Scope) (err error) {
  if u.ID == 1 {
    scope.DB().Model(u).Update("role", "admin")
  }
  return
}

注意 在 GORM 中 Save/Delete 操作默认是基于事务完成, 所以相关更改在提交事务之前是不可见的。 如果你想在你的 hooks 中看到这些变化,你可以在你的 hooks 中接收当前事务的参数,比如:

func (u *User) AfterCreate(tx *gorm.DB) (err error) {
  tx.Model(u).Update("role", "admin")
  return
}

更新对象时

Updating an object,更新对象时可用的 hooks

// begin transaction 开始事物
BeforeSave
BeforeUpdate
// save before associations 保存前关联
// update timestamp `UpdatedAt` 更新 `UpdatedAt` 时间戳
// save self 保存自己
// save after associations 保存后关联
AfterUpdate
AfterSave
// commit or rollback transaction 提交或回滚事务

代码实例:

func (u *User) BeforeUpdate() (err error) {
  if u.readonly() {
    err = errors.New("read only user")
  }
  return
}

// 在一个事务中更新数据
func (u *User) AfterUpdate(tx *gorm.DB) (err error) {
  if u.Confirmed {
    tx.Model(&Address{}).Where("user_id = ?", u.ID).Update("verfied", true)
  }
  return
}

删除对象时

Deleting an object,删除对象时可用的 hooks

// begin transaction 开始事务
BeforeDelete
// delete self 删除自己
AfterDelete
// commit or rollback transaction 提交或回滚事务

代码实例:

// 在一个事务中更新数据
func (u *User) AfterDelete(tx *gorm.DB) (err error) {
  if u.Confirmed {
    tx.Model(&Address{}).Where("user_id = ?", u.ID).Update("invalid", false)
  }
  return
}

查询对象时

Querying an object,查询对象时可用的 hooks

// load data from database 从数据库加载数据
// Preloading (eager loading) 预加载(加载)
AfterFind

代码实例:

func (u *User) AfterFind() (err error) {
  if u.MemberShip == "" {
    u.MemberShip = "user"
  }
  return
}

事务

GORM 默认会将单个的 create, update, delete操作封装在事务内进行处理,以确保数据的完整性。

如果你想把多个 create, update, delete 操作作为一个原子操作,Transaction 就是用来完成这个的。

事务

要在事务中执行一系列操作,通常您可以参照下面的流程来执行。

func CreateAnimals(db *gorm.DB) error {
  return db.Transaction(func(tx *gorm.DB) error {
    // 在事务中做一些数据库操作 (这里应该使用 'tx' ,而不是 'db')
    if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
      // 返回任意 err ,整个事务都会 rollback
      return err
    }

    if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {
      return err
    }

    // 返回 nil ,事务会 commit
    return nil
  })
}

手动控制的事务

// 开始事务
tx := db.Begin()

// 在事务中做一些数据库操作 (这里应该使用 'tx' ,而不是 'db')
tx.Create(...)

// ...

// 有错误时,手动调用事务的 Rollback()
tx.Rollback()

// 无错误时,手动调用事务的 Commit()
tx.Commit()

一个具体的例子

func CreateAnimals(db *gorm.DB) error {
  // 请注意,事务一旦开始,你就应该使用 tx 作为数据库句柄
  tx := db.Begin()
  defer func() {
    if r := recover(); r != nil {
      tx.Rollback()
    }
  }()

  if err := tx.Error; err != nil {
    return err
  }

  if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
     tx.Rollback()
     return err
  }

  if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {
     tx.Rollback()
     return err
  }

  return tx.Commit().Error
}

迁移

自动迁移

自动迁移你的模型,使之保持最新状态。

警告: 自动迁移 只会 创建表、缺失的列、缺失的索引, 不会 更改现有列的类型或删除未使用的列,以此来保护您的数据。

db.AutoMigrate(&User{})

db.AutoMigrate(&User{}, &Product{}, &Order{})

// 创建表时添加表后缀
db.Set("gorm:table_options", "ENGINE=InnoDB").AutoMigrate(&User{})

其它迁移工具

GORM 的自动迁移在大多数情况下都会正常工作,但如果你需要更严格的迁移工具, GORM 提供了通用 DB interface ,这可能对你有帮助。

// 返回 `*sql.DB`
db.DB()

更多详细信息请参阅 通用接口

模型方法

Has Table

// 检查模型 User 的表是否存在
db.HasTable(&User{})

// 检查表 users 是否存在
db.HasTable("users")

Create Table

// 为模型 `User` 创建表
db.CreateTable(&User{})

// 创建表时会追加 “ENGINE=InnoDB” 到 SQL 语句中。
db.Set("gorm:table_options", "ENGINE=InnoDB").CreateTable(&User{})

Drop table

// 删除模型 `User` 的表
db.DropTable(&User{})

// 删除表 `users`
db.DropTable("users")

// 删除模型 `User` 的表和表 `products`
db.DropTableIfExists(&User{}, "products")

ModifyColumn

修改列类型为给定的值

// 修改模型 `User` 的 description 列的类型为 `text` 
db.Model(&User{}).ModifyColumn("description", "text")

DropColumn

// 删除模型 `User` 的 description 列
db.Model(&User{}).DropColumn("description")

Add Indexes

// 为 `name` 列添加名为 `idx_user_name` 的普通索引
db.Model(&User{}).AddIndex("idx_user_name", "name")

// 为 `name` 和 `age` 两列添加名为 `idx_user_name_age` 的普通索引
db.Model(&User{}).AddIndex("idx_user_name_age", "name", "age")

// 添加唯一索引
db.Model(&User{}).AddUniqueIndex("idx_user_name", "name")

// 为多列添加唯一索引
db.Model(&User{}).AddUniqueIndex("idx_user_name_age", "name", "age")

Remove Index

// 删除索引
db.Model(&User{}).RemoveIndex("idx_user_name")

Add Foreign Key

// 添加外键
// 第一个参数: 外键字段
// 第二个参数:目标表名(字段)
// 第三个参数:删除时
// 第四个参数: 更新时
db.Model(&User{}).AddForeignKey("city_id", "cities(id)", "RESTRICT", "RESTRICT")

Remove ForeignKey

db.Model(&User{}).RemoveForeignKey("city_id", "cities(id)")

SQL 生成器

执行原生SQL

执行原生 SQL 时,不支持与其它方法的链式操作

db.Exec("DROP TABLE users;")
db.Exec("UPDATE orders SET shipped_at=? WHERE id IN (?)", time.Now(), []int64{11,22,33})

// Scan
type Result struct {
  Name string
  Age  int
}

var result Result
db.Raw("SELECT name, age FROM users WHERE name = ?", 3).Scan(&result)

sql.Rowsql.Rows

通过 *sql.Row*sql.Rows 获取查询结果

row := db.Table("users").Where("name = ?", "jinzhu").Select("name, age").Row() // (*sql.Row)
row.Scan(&name, &age)

rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Select("name, age, email").Rows() // (*sql.Rows, error)
defer rows.Close()
for rows.Next() {
  ...
  rows.Scan(&name, &age, &email)
  ...
}

// 原生 SQL
rows, err := db.Raw("select name, age, email from users where name = ?", "jinzhu").Rows() // (*sql.Rows, error)
defer rows.Close()
for rows.Next() {
  ...
  rows.Scan(&name, &age, &email)
  ...
}

sql.Rows 扫描至 model

rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Select("name, age, email").Rows() // (*sql.Rows, error)
defer rows.Close()

for rows.Next() {
  var user User
  // ScanRows 扫描一行记录到 user
  db.ScanRows(rows, &user)

  // do something
}
}

通过数据库接口 sql.DB

GORM 提供了 DB方法 ,可以从当前 *gorm.DB 连接内,获取一个通用的数据库接口*sql.DB

// 获取通用 sql.DB 并使用其方法
db.DB()

// Ping
db.DB().Ping()

注意 如果数据库底层连接的不是一个 *sql.DB(比如在一个事务中),那么该方法会返回 nil

连接池

// SetMaxIdleCons 设置连接池中的最大闲置连接数。
db.DB().SetMaxIdleConns(10)

// SetMaxOpenCons 设置数据库的最大连接数量。
db.DB().SetMaxOpenConns(100)

// SetConnMaxLifetiment 设置连接的最大可复用时间。
db.DB().SetConnMaxLifetime(time.Hour)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

行走的皮卡丘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值