Go框架详解 (一)Gorm | 青训营

296 阅读6分钟

Gorm

Gorm介绍

Gorm 是 Golang 的一个 ORM 框架。ORM 是通过实例对象的语法,完成关系型数据库的操作,是"对象-关系映射"(Object/Relational Mapping) 的缩写。使用 ORM 框架可以让我们更方便的操作数据库。

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

根据官方文档,Gorm的特性有:

  • 全功能 ORM
  • 关联 (Has One,Has Many,Belongs To,Many To Many,多态,单表继承)
  • Create,Save,Update,Delete,Find 中钩子方法
  • 支持 PreloadJoins 的预加载
  • 事务,嵌套事务,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快速使用

连接数据库

快速链接Mysql

package main
import (
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

func main() {
	dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
  // loc 设置本地系统的时间, 前提 parseTime=True
  // 更多参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	//db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{Logger:logger.Default.LogMode(logger.Info)})  打印sql日志
	if err != nil {
		panic(err) // 如果数据库不存在会报错
	}
}

Gorm中可以通过定义一个结构体表示模型,gorm.Model来继承 GORM 中的基础模型(包含 ID、Created_at、Updated_at 和 Deleted_at 字段),最后使用 AutoMigrate 方法将结构体映射到数据库中的表。

type User struct {
    gorm.Model
    Name string
    Age  int
}

db.AutoMigrate(&User{})

增删改查操作

Gorm中执行基础的CRUD操作如下:

// 创建记录
user := User{Name: "John", Age: 30}
db.Create(&user)

// 查询记录
var users []User
db.Find(&users)  //查询所有user表
db.Where("name = ?", "John").First(&users) //查询出符合条件的数据

db.First(&user, 1) //根据整形主键查找一条数据
db.First(&user, "name = ?", "John") //查找name字段为John的一条数据

// 更新记录
user.Name = "Tom"
db.Save(&user)

user := User{}
db.Model(&user).Update("name","啊") //更新所有的数据的name,name="啊"

// 删除记录
db.Delete(&user)

执行原生SQL语句

Gorm中可以通过Raw方法执行原生SQL语句。该方法接收一个 SQL 查询和任意数量的参数,并返回一个 *gorm.DB 对象,可以继续链式调用其他 GORM 方法。通过调用 .Scan() 方法可以将查询结果映射到相应的结构体中。 由于直接执行原始 SQL,所以需要手动处理 SQL 注入、参数绑定和结果集映射等问题。

此外还能使用Exec方法。DB.Exec() 方法用于执行原始 SQL 语句或可执行的命令。它不返回查询结果,而是返回一个 *sql.Result 对象,其中包含了执行结果的信息,例如受影响的行数。DB.Exec() 方法通常用于执行 INSERT、UPDATE、DELETE 等修改操作,并用于判断操作是否成功。

// Row()方法示例:
row := DB.Raw("SELECT * FROM users WHERE age > ?", 18).Row() 
var user User 
row.Scan(&user.Name, &user.Age)

// Exec()方法示例:
result := DB.Exec("UPDATE users SET name = ? WHERE id = ?", "John", 1) 
rowsAffected, err := result.RowsAffected()

DB.Raw() 返回 *sql.Rows 结果集对象,需要手动处理结果集的扫描和映射。 DB.Exec() 返回 *sql.Result 对象,主要用于获取执行结果的信息。 在使用这两个方法时,需要注意以下事项:

  • 由于原始 SQL 可能带来安全风险,请确保输入的数据经过正确的验证和转义,以防止 SQL 注入攻击。 在使用 DB.Raw() 或 DB.Exec() 方法之前,确保已经建立了有效的数据库连接,并且对应的数据库表和列名存在且拼写正确。

联合查询

Gorm中还可以使用Joins方法执行联合查询。 该方法接收一个表名和一个条件语句,其中表名表示要联合查询的表,条件语句可以是字符串、结构体或 map 类型。

var user User
var email Email

db.Joins("JOIN emails ON users.id = emails.user_id").Where("users.name = ?", "John").Find(&user, &email)

另一种方法: db.Table("查询表名称").Select("查询字段").Joins("left join xxxx 联表信息").Scan( 查询结果的[]结构体)。注意: 结构体 字段标签要加 gorm:"column:hostId" , ( )内容对应正常写的sql就可以了

Gorm事务

Gorm提供Begin、Commit、Rollback方法用于使用事务。例如:

// 通过Begin方法开启事务,后续所有在事务中的操作都在tx下进行
tx := db.Begin()

// 在事务中执行插入操作
if err := tx.Create(&User{Name: "John", Age: 30}).Error; err != nil {
    // 发生错误时回滚事务
    tx.Rollback()
}

// 成功时提交事务
tx.Commit()

事务相关的方法总结如下:

事务方法说明
tx := db.Begin()开始事务
tx.(db操作)在事务中执行一些 db 操作
tx.Rollback()遇到错误时回滚事务
tx.SavePoint()设置保存点标记
tx.RollbackTo()回滚到保存点标记
tx.Commit()提交事务

以上介绍的是Gorm中手动事务的相关方法,Gorm同时也能借助Tanscaction方法更便利的实现事务。例如:

type User struct {
	gorm.Model
	Name string
}

func main() {
	db.AutoMigrate(&User{})
	db.Transaction(func(tx *gorm.DB) error {
		// 在事务中执行一些 db 操作(从这里开始,应该使用 'tx' 而不是 'db')
		if err := tx.Create(&User{Name: "Giraffe"}).Error; err != nil {
			// 返回任何错误都会回滚事务
			return err
		}

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

		// 返回 nil 提交事务
		return nil
	})
}

手动事务适用于小事务操作,出错了直接全部回滚会更好,虽然提供了 SavePointRollbackto方法,来提供保存点以及回滚至保存点功能,但是有一些同步操作操作很不方便。
GORM自带事务适用大事务操作,可以使用嵌套事务。

事务处理时需要注意的点

  1. 首先启动事务时一定要做错误判断
  2. 建议在启动事务之后马上写defer方法
  3. 在defer方法内对err进行判断,如果全局中有err!=nil就回滚
  4. 全局中err都为nil则提交事务
  5. 在提交事务之后我们可以定义一个钩子函数afterCommit,来统一处理事务提交后的逻辑。

示例代码如下:

tx, err := g.DB().Begin() //启动事务
if err != nil {
   return errors.New("启动事务失败")
}

defer func() {
   if err != nil {
      tx.Rollback() //回滚
   } else {
      tx.Commit()
      //定义钩子函数
      afterCommmit()
   }
}()

Hock

Hook 是在创建、查询、更新、删除等操作之前、之后调用的函数。如果您已经为模型定义了指定的方法,它会在CURD时自动被调用。如果任何回调返回错误,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) {
  if u.ID == 1 {
    tx.Model(u).Update("role", "admin")
  }
  return
}

Gorm性能优化

对于写操作(创建、更新、删除),为了确保数据的完整性,GORM 会将它们封装在一个事务里。但这会降低性能,可以用SkipDefaultTransaction关闭默认事务。

同时还能在执行任何 SQL 时都创建并缓存预编译语句,可以提高后续的调用速度。

db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
  SkipDefaultTransaction: true,
  PrepareStmt: true,
})