GORM 框架入门

这是我参与「第五届青训营 」笔记创作活动的第 14 天

前言

前两篇笔记分别介绍了 Golang 微服务 HTTP 框架 Hertz 和 Golang 微服务 RPC 框架 Kitex,本文将要介绍面向golang语言的一种ORM(持久层)框架 GORM。

重点内容

  • GORM 简介

  • GORM 快速入门

  • GORM CRUD 接口介绍

知识点介绍

GORM 简介

GORM 是面向 Golang 语言的一种 ORM(持久层)框架,支持多种数据库的接入,例如 MySQL,PostgreSQL,SQLite,SQL Server,Clickhouse。此框架的特点,弱化了开发者对于 SQL 语言的掌握程度,使用提供的 API 进行底层数据库的访问。

特性:

  • 全功能 ORM

  • 关联 (Has One,Has Many,Belongs To,Many To Many,多态,单表继承)

  • Create,Save,Update,Delete,Find 中钩子方法

  • 支持 Preload、Joins 的预加载

  • 事务,嵌套事务,Save Point,Rollback To Saved Point

  • Context,预编译模式,DryRun 模式

  • 批量插入,FindInBatches,Find/Create with Map,使用 SQL 表达式、Context Valuer 进行 CRUD

  • SQL 构建器,Upsert,数据库锁,Optimizer/Index/Comment Hint,命名参数,子查询

  • 复合主键,索引,约束

  • Auto Migration

  • 自定义 Logger

  • 灵活的可扩展插件 API:Database Resolver(多数据库,读写分离)、Prometheus…

  • 每个特性都经过了测试的重重考验

  • 开发者友好

GORM 快速入门

安装

GORM 支持多种数据库的接入,这里以 MySQL 为例:

 go get -u gorm.io/gorm
 go get -u gorm.io/driver/mysql

快速入门

 package main
 ​
 import (
     "fmt"
     "github.com/jinzhu/gorm"
     _ "github.com/jinzhu/gorm/dialects/mysql"
 )
 ​
 type Product struct { 
    Code  string
    Price uint
 }
 ​
 func(p Product) TableName() string { 
    return"product"// 返回的字符串即表名
 }
 ​
 funcmain() {
    // 连接数据库(需要传递一个 dsn)
    db, err := gorm.Open(
        mysql.Open(dsn:"user:pass@(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})
 ​
    // 查询数据 先声明一个结构体, 然后使用 First() 方法(注意是传递指针)
    var product Product
    db.First(&product, 1)                 // 根据整型主键查找
    db.First(&product, "code = ?", "D42") // 查找 code 字段值为 D42 的记录
 ​
    // 更新数据 - 用 Update() 方法将 product 的 price 更新为 200
    db.Model(&product).Update("Price", 200)
    // 更新数据 - 用 Updates() 方法更新多个字段
    db.Model(&product).Updates(Product{Price: 200, Code: "F42"})                    // 仅更新非零值字段
    db.Model(&product).Updates(map[string]interface{}{"Price": 200, "Code": "F42"}) // 传递 map 可以更新零值
 ​
    // 删除数据
    db.Delete(&product, 1)
 }

GORM 支持的数据库

GORM 官方支持的数据库类型有: MySQL, PostgreSQL, SQlite, SQL Server

连接 SQLServer 数据库为例:

 import (
   "gorm.io/driver/sqlserver"
   "gorm.io/gorm"
 )
 ​
 // github.com/denisenkom/go-mssqldb
 dsn := "sqlserver://gorm:LoremIpsum86@localhost:9930?database=gorm"
 db, err := gorm.Open(sqlserver.Open(dsn), &gorm.Config{})

GORM 通过驱动来连接数据库,如果需要连接其他类型的数据库,可以复用或自行开发驱动。

GORM CRUD 接口介绍

创建

  • 创建一条记录

 p := &Product{Code: "D42"}
 res := db.Create(p)    // 因为 gorm 是链式调用, 使用 Creat() 后会返回一个gorm对象
 fmt.Println(res.Error) // 获取 err
 fmt.Println(p.ID)      // 返回插入数据的主键
  • 创建多条记录

 products := []*Product{{Code: "D41"}, {Code: "D42"}, {Code: "D43"}}
 res = db.Create(products) // 批量创建数据
 fmt.Println(res.Error)    // 获取 err
 for _, p := range products {
    fmt.Println(p.ID)
 }
  • 可以通过标签 default 为字段定义默认值

 type User struct {
   ID         int64
   Name       string`gorm:"default:galeone"`
   Age        int64  `gorm:"default:18"`
   uuid.UUID  UUID   `gorm:"type:uuid;default:gen_random_uuid()"`// 数据库函数
 }
  • GORM 为不同数据库提供了兼容的 Upsert 支持

 // 不处理冲突
 DB.Clauses(clause.OnConflict{DoNothing: true}).Create(&user)

查询

  • 查询单个对象:GORM 提供了 First、Take、Last 方法,以便从数据库中检索单个对象。当查询数据库时它添加了 LIMIT 1 条件,且没有找到记录时,它会返回 ErrRecordNotFound 错误

 // 获取第一条记录(主键升序)
 db.First(&user)
 //SELECT*FROM users ORDERBY id LIMIT 1;
 ​
 // 获取一条记录,没有指定排序字段
 db.Take(&user)
 //SELECT*FROM users LIMIT 1;
 ​
 // 获取最后一条记录(主键降序)
 db.Last(&user)
 //SELECT*FROM users ORDERBY id DESC LIMIT 1;
 ​
 result := db.First(&user)
 result.RowsAffected // 返回找到的记录数
 result.Error        //returns error
 ​
 // 检查 ErrRecordNotFound 错误
 errors.Is(result.Error, gorm.ErrRecordNotFound)

根据主键检索

db.First(&user, 10)
//SELECT*FROM users WHERE id =10;

db.First(&user, "10")
//SELECT*FROM users WHERE id =10;

db.Find(&users, []int{1,2,3})
//SELECT*FROM users WHERE id IN (1,2,3);
  • 条件查询

  • String 条件

// 获取第一条匹配的记录
db.Where("name = ?", "jinzhu").First(&user)
//SELECT*FROM users WHERE name ='jinzhu'ORDERBY id LIMIT 1;

// 获取全部匹配的记录
db.Where("name <> ?", "jinzhu").Find(&users)
//SELECT*FROM users WHERE name <>'jinzhu';

//IN
db.Where("name IN ?", []string{"jinzhu", "jinzhu 2"}).Find(&users)
//SELECT*FROM users WHERE name IN ('jinzhu','jinzhu 2');

//LIKE
db.Where("name LIKE ?", "%jin%").Find(&users)
//SELECT*FROM users WHERE name LIKE'%jin%';

//AND
db.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users)
//SELECT*FROM users WHERE name ='jinzhu'AND age >=22;

//Time
db.Where("updated_at > ?", lastWeek).Find(&users)
//SELECT*FROM users WHERE updated_at >'2000-01-01 00:00:00';

//BETWEEN
db.Where("created_at BETWEEN ? AND ?", lastWeek, today).Find(&users)
//SELECT*FROM users WHERE created_at BETWEEN'2000-01-01 00:00:00'AND'2000-01-08 00:00:00';
  • Struct & Map 条件:注意 当使用结构作为条件查询时,GORM 只会查询非零值字段。这意味着如果您的字段值为 0、''、false 或其他零值,该字段不会被用于构建查询条件

// Struct
db.Where(&User{Name: "jinzhu", Age: 20}).First(&user)
// SELECT * FROM users WHERE name = "jinzhu"AND age = 20ORDERBY id LIMIT 1;

// Map
db.Where(map[string]interface{}{"name": "jinzhu", "age": 20}).Find(&users)
// SELECT * FROM users WHERE name = "jinzhu"AND age = 20;

// 主键切片条件
db.Where([]int64{20, 21, 22}).Find(&users)
// SELECT * FROM users WHERE id IN (20, 21, 22);
  • 子查询

db.Where("amount > ?", db.Table("orders").Select("AVG(amount)")).Find(&orders)
//SELECT*FROM "orders" WHERE amount > (SELECTAVG(amount) FROM "orders");

subQuery := db.Select("AVG(age)").Where("name LIKE ?", "name%").Table("users")
db.Select("AVG(age) as avgage").Group("name").Having("AVG(age) > (?)", subQuery).Find(&results)
//SELECTAVG(age) as avgage FROM `users` GROUPBY `name` HAVINGAVG(age) > (SELECTAVG(age) FROM `users` WHERE name LIKE "name%

更新

  • 更新单个列

// 根据条件和 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=111AND active=true;
  • 更新多个列

  • 根据 struct 更新属性

// 根据 `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更新属性

// 根据 `map` 更新属性
db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 18, "actived": false})
//UPDATE users SET name='hello', age=18, actived=false, updated_at='2013-11-17 21:34:10'WHERE id=111;
  • SQL 表达式更新

// product 的 ID 是 `3`
DB.Model(&product).Update("price", gorm.Expr("price * ? + ?", 2, 100))
//UPDATE "products" SET "price" = price *2+100, "updated_at" ='2013-11-17 21:34:10'WHERE "id" =3;

DB.Model(&product).Updates(map[string]interface{}{"price": gorm.Expr("price * ? + ?", 2, 100)})
//UPDATE "products" SET "price" = price *2+100, "updated_at" ='2013-11-17 21:34:10'WHERE "id" =3;

DB.Model(&product).UpdateColumn("quantity", gorm.Expr("quantity - ?", 1))
//UPDATE "products" SET "quantity" = quantity -1WHERE "id" =3;

DB.Model(&product).Where("quantity > 1").UpdateColumn("quantity", gorm.Expr("quantity - ?", 1))
//UPDATE "products" SET "quantity" = quantity -1WHERE "id" =3AND quantity >1;

删除

  • 物理删除

db.Delete(&User{}, 10)
//DELETEFROM users WHERE id =10;

db.Delete(&User{}, "10")
//DELETEFROM users WHERE id =10;

db.Delete(&users, []int{1,2,3})
//DELETEFROM users WHERE id IN (1,2,3);
  • 软删除:如果模型包含了一个 gorm.deletedat 字段(gorm.Model 已经包含了该字段),将自动获得软删除的能力!拥有软删除能力的模型调用 Delete 时,记录不会被从数据库中真正删除。但 GORM 会将 DeletedAt 置为当前时间, 并且你不能再通过正常的查询方法找到该记录。

type User struct {
  ID      int
  Deleted gorm.DeletedAt
  Name    string
}
//user 的 ID 是 `111`
db.Delete(&user)
//UPDATE users SET deleted_at="2013-10-29 10:23" WHERE id =111;

// 批量删除
db.Where("age = ?", 20).Delete(&User{})
//UPDATE users SET deleted_at="2013-10-29 10:23" WHERE age =20;

// 在查询时会忽略被软删除的记录
db.Where("age = 20").Find(&user)
//SELECT*FROM users WHERE age =20AND deleted_at ISNULL;

可以使用 Unscoped 找到被软删除的记录

db.Unscoped().Where("age = 20").Find(&users)
// SELECT * FROM users WHERE age = 20;

事务

GORM 提供了 Begin、Commit、Rollback 方法用于使用事务

tx := db.Begin() // 开始事务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
}
// 提交事务
tx.Commit()

GORM 提供了 Tansaction 方法用于自动提交事务,避免用户漏写Commit、 Rollback

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 提交事务returnnil
})

Hook

Hook 是在创建、查询、更新、删除等操作之前、之后调用的函数。

如果您已经为模型定义了指定的方法,它会在创建、更新、查询、删除时自动被调用。如果任何回调返回错误,GORM 将停止后续的操作并回滚事务。

钩子方法的函数签名应该是 func(*gorm.DB) error

代码示例:

func(u *User) BeforeCreate(tx *gorm.DB)(err error) {
  u.UUID = uuid.New()if !u.IsValid() {
    err = errors.New("can't save invalid data")
  }
  return
}

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

总结

本文主要介绍了 GORM 框架的相关基础知识,介绍了常用的接口函数,具体的细节还是需要仔细研究官方文档。这三个框架的学习都可以看出官方文档的重要性,同时学习框架还是要多上手练习才行。

引用

  1. GORM 官方文档 GORM 指南 | GORM - The fantastic ORM library for Golang, aims to be developer friendly.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值