GORM框架 | 青训营笔记

80 阅读5分钟

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

ORM(对象关系映射): 把数据库映射成对象。ORM 使用对象,封装了数据库操作,因此可以不碰 SQL 语言。开发者只使用面向对象编程,与数据对象直接交互,不用关心底层数据库实现;

gorm是使用go语言实现数据库访问的ORM库。使用这个库,我们可以利用面向对象的方法,更加方便的对数据库中的数据进行CRUD(数据库的基本操作有四种:createreadupdatedelete,简称 CRUD),而无需关心底层数据库的具体实现以及数据库的类型(支持多种类型的数据库,例如MySQL,SQLite,PostgreSQL

下载依赖

$go get github.com/jinzhu/gorm
$go get github.com/jinzhu/gorm/dialects/mysql

第一个是核心库。 第二个是mysql数据库的驱动包,如果使用的是其他数据库,则需下载相应数据库的驱动包。

gorm包装了一些常用数据库的驱动,直接下载即可:

//import _ "github.com/jinzhu/gorm/dialects/mysql"
//import _ "github.com/jinzhu/gorm/dialects/postgres"
//import _ "github.com/jinzhu/gorm/dialects/sqlite"
//import _ "github.com/jinzhu/gorm/dialects/mssql"

连接数据库

packae main
import (
  "github.com/jinzhu/gorm"
  _ "github.com/jinzhu/gorm/dialects/mysql"
  "fmt"
)
func main() {
  db, err := gorm.Open("mysql","root:12345@/demo?charset=utf8&parseTime=True&loc=Local")
  if err != nil {
      fmt.Println(err)
      return
  }
  
  defer db.Close()
  // Migrate the schema
  db.AutoMigrate(&Product{})
  
  // Create
  db.Create(&Product{Code: "L1212", Price: 1000})

  // Read
  var product Product
  db.First(&product, 1) // find product with id 1
  db.First(&product, "code = ?", "L1212") // find product with code l1212

  // Update - update product's price to 2000
  db.Model(&product).Update("Price", 2000)

  // Delete - delete product
  db.Delete(&product)
}

上面代码中 mysql 是数据库驱动,root 是数据库用户名,12345 是密码,demo 则是数据库名称。

增加数据

type User struct {
  gorm.Model
  ID     int            `gorm:"primary_key"`
  Name   string         `gorm:"not_null"`
}

func add() {
  user := &User{Name:"David"}
  db.Create(user)
}

User类型中内嵌了gorm.Model字段,gorm.Model会自动为User{}添加如下4个字段:

id:索引

create_at:数据条目生成时间

updated_at:数据条目更新时间

deleted_at:数据条目删除时间

查询

普通场景:简单查询用Find+Where的函数结合实现,结合Limit+Offset+Order实现分页等高频功能;

追求性能:可以引入Select避免查询所有字段,但会导致返回结果部分字段不存在的奇怪现象,需要权衡;

复杂查询:例如Join+子查询等,推荐使用下面的原生SQL,用GORM拼接的体验并不好。

使用GORM的核心思路梳理 一个对象 = 一行数据 示例中的一个User对象,完整地对应到具体users表中的一行数据,让整个框架更加清晰明了。每当数据库增加了一列,就对应地在结构体中加一个字段。这里有两个注意点:

不要在核心结构体User中加入非表中的数据,如一些计算的中间值,引起二义性;

gorm.Model可以提升编码效率(会减少重复编码),但会限制数据库表中字段的定义,慎用(个人更希望它能开放成一个接口);

选择生效字段 = 核心结构体 + 字段数组 在 查询 和 更新 接口里,我推荐的使用方法是采用核心结构体User+一个fields的数组,前者保存具体的数据、也实现了结构体复用,后者则选择生效的字段。

这种风格代码和Google推荐的API风格非常像,可读性很棒。

这里还遗留了一个问题,就是fields数组里的字符串必须手输,可以考虑结合go generate自动生成这些fields的字符串常量,减少出错的概率。

缩短链式调用 GORM的主要风格是链式调用,类似于Builder设计模式、串联堆起一个SQL语句。这种调用方式扩展性很强,但会带来了一个很严重的问题:容易写出一个超长的链式调用,可维护成本大幅度提高。

所以,在我的推荐使用方式里,区分了两种场景:

简单场景 - 核心结构体 + 字段数组

复杂场景 - 原生SQL

聚焦微服务的场景 作为一个ORM工具,GORM要考虑兼容各种SQL语句,内部非常庞大的。但如今更多地是考虑微服务的场景,这就能抛开大量的历史包袱,实现得更加简洁。这里我简单列举三个不太推荐使用的SQL特性:

减少group by - 考虑将聚合字段再单独放在一个表中

抛弃join - 多表关联采用多次查询(先查A表,然后用In语句去B表查)、或做一定的字段冗余(即同时放在A、B两个表里)

抛弃子查询,将相关逻辑放在代码里

当然,真实业务研发过程中无法完全避免复杂SQL,我们只能有意识地减少引入复杂度。

避免引入非原生MySQL的特性 GORM除了常规的SQL功能,还提供了一些高级特性、模型关联、钩子等,非常炫酷。

但我不推荐大家在实际项目中使用这些特性。只有尽可能地保证这个框架简洁,才能保证代码后续的可维护性。

熟悉MySQL历史的朋友都知道,存储过程在以前相当一段时间都是很好的工具,但如今都倡导去存储过程。GORM的这些特性和存储过程有异曲同工之处:一个将业务逻辑放在了数据库,另一个则放到了ORM框架里,会导致后续的迁移成本变高。