ORM 框架

640 阅读5分钟

一. 什么是 orm 框架?

对象关系映射(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。ORM框架是连接数据库的桥梁,只要提供了持久化类与表的映射关系,ORM框架在运行时就能参照映射文件的信息,把对象持久化到数据库中。

二. 为什么使用ORM?

在最开始没有 orm 的时候,开发者们在开发过程种与数据库的交互常常避免不了以下步骤:

  • 手写 SQL

    • 问题

      • 容易出错
      • 难以重构
  • 手动处理结果集

    • 问题

      • 过多的样板代码
      • 开发者的将过多的精力投入到跟业务没关系的地方

如单纯的使用 database/sql 操作数据库如下

type TestModel struct {
    Id        int64
    FirstName string
    Age       int8
    LastName  *sql.NullString
}
​
func Open(driver string, dsn string) (*sql.DB, error) {
    db, err := sql.Open(driver, dsn)
    if err != nil {
        return nil, err
    }
    return db, nil
}
​
func main() {
    db, err := Open("mysql", "root:123456@tcp(localhost:3306)/integration_test")
    if err != nil {
        panic(err)
    }
    rows, err := db.QueryContext(context.Background(), "SELECT `id`,`first_name` FROM `test_model` LIMIT 1;")
    if err != nil {
        panic(err)
    }
    for rows.Next() {
        tm := &TestModel{}
        err = rows.Scan(&tm.Id, &tm.FirstName)
        if err != nil {
            panic(err)
        }
        fmt.Println(tm)
    }
}
​

所以我们就希望有这么一个东西, 帮助我们完成这两个步骤,这也就 是 ORM 诞生的初衷。

三. ORM 框架主要职

image.png

  • 对象 -> SQL

    当用户输入一个对象的时候,能够产生对应的 SQL。比如在插入场景下,一个 User 对象应该生成一条 INSERT INTO 语句

  • 结果集 -> 对象

    当收到数据库返回的行时,能 够将行组装成对象,并返回给用户。例如一条 SELECT 语句查出来一条数据,并将该数据组装 成一个 User 对象返回给用户

四. GO 主流的 ORM 框架

4.1 Beego orm

  • 入门例子

    package beego
    import (
        "github.com/beego/beego/v2/client/orm"
        _ "github.com/mattn/go-sqlite3"
        "testing"
    )
    ​
    // User -
    type User struct {
        ID   int    `orm:"column(id)"`
        Name string `orm:"column(name)"`
    }
    ​
    // 注册模型、驱动以及 DB
    func init() {
        // need to register models in init
        orm.RegisterModel(new(User))
    ​
        // need to register db driver
        orm.RegisterDriver("sqlite3", orm.DRSqlite)
    ​
        // need to register default database
        orm.RegisterDataBase("default",
            "sqlite3", "beego.db")
    }
    ​
    // 创建 ORM 实例
    func TestCRUD(t *testing.T) {
        // automatically build table
        orm.RunSyncdb("default", false, true)
    ​
        // create orm object
        o := orm.NewOrm()
    ​
        // data
        user := new(User)
        user.Name = "mike"// insert data
        o.Insert(user)
    }
    
  • 源码解析

  • 元数据

    元数据是对模型的描述, 在 Beego 里分成了 modelInfo -> fields -> fieldInfo 三个层级

image.png

image.png

image.png - 查询接口

image.png

image.png

image.png

image.png

  • 事务接口

    • 事务接口分成两类:

      • 普通的 Begin、Commit 和 Rollback

      • 闭包形式的 DoXX

image.png

image.png

image.png

4.2 Gorm

  • 入门例子

    package gorm
    ​
    import (
        "gorm.io/driver/sqlite"
        "gorm.io/gorm"
        "testing"
    )
    ​
    type Product struct {
        gorm.Model
        Code  string `gorm:"column(code)"`
        Price uint
    }
    ​
    func  (p Product) TableName() string {
        return "product_t"
    }
    ​
    func (p *Product) BeforeSave(tx *gorm.DB) (err error) {
        println("before save")
        return
    }
    ​
    func (p *Product) AfterSave(tx *gorm.DB) (err error) {
        println("after save")
        return
    }
    ​
    func (p *Product) BeforeCreate(tx *gorm.DB) (err error) {
        println("before create")
        return
    }
    ​
    func (p *Product) AfterCreate(tx *gorm.DB) (err error) {
        println("after create")
        // 刷新缓存
        return
    }
    ​
    func (p *Product) BeforeUpdate(tx *gorm.DB) (err error) {
        println("before update")
        return
    }
    ​
    func (p *Product) AfterUpdate(tx *gorm.DB) (err error) {
        println("after update")
        // 刷新缓存
        return
    }
    ​
    func (p *Product) BeforeDelete(tx *gorm.DB) (err error) {
        println("before update")
        return
    }
    ​
    func (p *Product) AfterDelete(tx *gorm.DB) (err error) {
        println("after update")
        return
    }
    ​
    func  (p *Product) AfterFind(tx *gorm.DB) (err error) {
        println("after find")
        return
    }
    ​
    func TestCRUD(t *testing.T) {
        db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
        if err != nil {
            panic("failed to connect database")
        }
        // 打印 SQL,但不执行
        db.DryRun = true// Migrate the schema
        db.AutoMigrate(&Product{})
    ​
        // Create
        db.Create(&Product{Code: "D42", Price: 100})
    ​
    ​
        // Read
        var product Product
        db.First(&product, 1) // find product with integer primary key
        db.First(&product, "code = ?", "D42") // find product with code D42// Update - update product's price to 200
        db.Model(&product).Update("Price", 200)
        // Update - update multiple fields
        db.Model(&product).Updates(Product{Price: 200, Code: "F42"}) // non-zero fields
        db.Model(&product).Updates(map[string]interface{}{"Price": 200, "Code": "F42"})
    ​
        // Delete - delete product
        db.Delete(&product, 1)
    }
    
  • 源码解析

  • 元数据

    • GORM 的元数据看起来和 Beego ORM元数据差不多,很难理解结构体里每一个字段的作用。 一个比较大的不同是 GORM 只有 Schame-> Field 两级。

image.png

image.png

  • 查询接口

image.png

image.png

image.png

  • 事务接口

    • Begin、Commit、Rollback
    • 闭包接口
    • save point 支持

image.png

4.3 Ent

Ent 和 Beego ORM、GORM 有一个设计理念上 的本质区别,就是 Ent 采用的是代码生成技术

image.png

  • 入门例子

    package ent
    ​
    import (
        "context"
        "entgo.io/ent/dialect"
        "gitee.com/geektime-geekbang/geektime-go/orm/ent/ent"
        _ "github.com/mattn/go-sqlite3"
        "log"
        "testing"
    )
    ​
    func TestEntCURD(t *testing.T) {
        // Create an ent.Client with in-memory SQLite database.
        client, err := ent.Open(dialect.SQLite, "file:ent?mode=memory&cache=shared&_fk=1")
        if err != nil {
            log.Fatalf("failed opening connection to sqlite: %v", err)
        }
        defer client.Close()
        ctx := context.Background()
        // Run the automatic migration tool to create all schema resources.
        if err := client.Schema.Create(ctx); err != nil {
            log.Fatalf("failed creating schema resources: %v", err)
        }
        err = client.User.Create().Exec(context.Background())
    }
    

五. ORM 核心功能模块

image.png

  • SQL:必须要支持的就是增删改查,DDL 一般是作为 一个扩展功能,或者作为一个工具来提供。
  • 映射:将结果集封装成对象,性能瓶颈。
  • 事务:主要在于维护好事务状态。
  • 元数据:SQL 和映射两个部分的基石。
  • AOP:处理横向关注点。
  • 关联关系:部分 ORM 框架会提供,性价比低。
  • 方言:兼容不同的数据库,至少要兼容 MySQL、 SQLite、 PostgreSQL。

六. 总结

  • ORM 框架的核心是什么SQL 构造处理结果集。在别的语言里面,因为底层库可能不够强大, ORM 框架还要解决连接和会话管理的问题,Go 是不需要的。
  • ORM 是什么?ORM 是指对象关系映射,一般是指用于语言对象和关系型数据库行相互转化的工具
  • 为什么要使用 ORM 框架?本质上来说,不使用 ORM 框架也是可以的,但我们就要花费很多时间在处理拼接 SQL、处理返回的行上。这些本身是不难但很琐碎的事情,所以我们需要一个 ORM 框架来帮我们解决这两个问题。
  • ORM 的优点?ORM 的优点就是 API 对编程更加友好,开发效率更高。
  • 使用 ORM 性能会更好吗?显然不能,直接写 SQL 的性能最好。
  • ORM 怎么使用缓存?利用 AOP 来拦截到查询,而后嵌入缓存。