使用 GORM(Go 的 ORM 库)连接数据库,并实现增删改查操作 |青训营

118 阅读6分钟

使用 GORM(Go 的 ORM 库)连接数据库,并实现增删改查操作

Gorm介绍

Gorm是一个已经迭代了10年+功能强大的ORM框架,在字节内部被广泛使用并且拥有非常丰富的开源扩展。

Gorm的基本使用:

安装

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

快速入门

package main

import (
  "gorm.io/gorm"
  "gorm.io/driver/sqlite"
)

//定义gorm model
type Product struct {
  gorm.Model
  Code  string
  Price uint
}

//为model定义表名
func (p Product) TableName() string {
  return "product"
}

func main() {
  
  //连接数据库
  db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
  if err != nil {
    panic("failed to connect database")
  }

  // 迁移 schema
  db.AutoMigrate(&Product{})

  // Create
  db.Create(&Product{Code: "D42", Price: 100})

  // Read
  var product Product
  db.First(&product, 1) // 根据整型主键查找
  db.First(&product, "code = ?", "D42") // 查找 code 字段值为 D42 的记录

  // Update - 将 product 的 price 更新为 200
  db.Model(&product).Update("Price", 200)
  // Update - 更新多个字段
  db.Model(&product).Updates(Product{Price: 200, Code: "F42"}) // 仅更新非零值字段
  db.Model(&product).Updates(map[string]interface{}{"Price": 200, "Code": "F42"})

  // Delete - 删除 product
  db.Delete(&product, 1)
}

Gorm的约定(默认)

Gorm使用名为ID的字段作为主键

使用结构体的蛇形复数作为表名

字段名的蛇形作为列名

使用CreateAt、UpdateAt字段作为创建、更新时间

GORM支持的数据库

GORM目前支持MySQL、SQLServer、PostgreSQL、SQLite。

连接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创建数据

创建记录:

user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}

result := db.Create(&user) // 通过数据的指针来创建

user.ID             // 返回插入数据的主键
result.Error        // 返回 error
result.RowsAffected // 返回插入记录的条数

创建多项纪录:

users := []*User{
    User{Name: "Jinzhu", Age: 18, Birthday: time.Now()},
    User{Name: "Jackson", Age: 19, Birthday: time.Now()},
}

result := db.Create(users) // pass a slice to insert multiple row

result.Error        // returns error
result.RowsAffected // returns inserted records count

批量插入:

var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}
db.Create(&users)

for _, user := range users {
  user.ID // 1,2,3
}

如何处理数据冲突?

使用Upsert,clause.OnConflict处理数据冲突

import "gorm.io/gorm/clause"

// 以不处理冲突为例,创建一条数据
db.Clauses(clause.OnConflict{DoNothing: true}).Create(&user)

默认值:使用default标签

type User struct {
  ID   int64
  Name string `gorm:"default:galeone"`
  Age  int64  `gorm:"default:18"`
}

GORM查询数据

First查询:获取第一条记录,查询不到数据会返回ErrRecordNotFound。

// 获取第一条记录(主键升序)
db.First(&user)
// SELECT * FROM users ORDER BY id LIMIT 1;

使用Find查询多条数据,查询不到数据不会返回错误。

users := make([]*User, 0)
result := db.Where("age > 10").Find(&users)//SELECT * FROM users where age > 10;
fmt.Println(result.RowsAffected)//返回找到的记录数,相当于‘len(users)'
fmt.Println(result.Error)//return error

string条件检索:

// 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;

使用结构体作为查询条件时,GORM只会查询非零字段。这意味着如果您的字段值为0、‘’、false或其他零值,该字段不会被用于构建查询条件,使用Map来构建查询条件。

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

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

GORM更新数据

更新单个列:

// Update with conditions
db.Model(&User{}).Where("active = ?", true).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE active=true;

更新多列:

// Update attributes with `struct`, will only update non-zero fields
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;

// Update attributes with `map`
db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET name='hello', age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;

更新选定字段:

// Select with Map
// User's ID is `111`:
db.Model(&user).Select("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET name='hello' WHERE id=111;

SQL表达式更新:

// product's ID is `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;

使用Struct更新时,只会更新非零值,如果需要更新零值可以使用Map更新或使用Select选择字段。

GORM删除数据

物理删除:

// Email 的 ID 是 `10`
db.Delete(&email)
// DELETE from emails where id = 10;

// 带额外条件的删除
db.Where("name = ?", "jinzhu").Delete(&email)
// DELETE from emails where id = 10 AND name = "jinzhu";

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

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

db.Delete(&users, []int{1,2,3})
// DELETE FROM users WHERE id IN (1,2,3);

db.Where("email LIKE ?", "%jinzhu%").Delete(&Email{})
// DELETE from emails where email LIKE "%jinzhu%";

db.Delete(&Email{}, "email LIKE ?", "%jinzhu%")
// DELETE from emails where email LIKE "%jinzhu%";

软删除:

GORM提供了gorm.DeletedAt用于帮助用户实现软删

拥有软删除能力的Model调用Delete时,记录不会被从数据库中真正删除。但GORM会将DeletedAt置为当前时间,并且你不能再通过正常的查询方法找到该记录。

使用Unscoped可以查询到被软删的数据

type User struct {
  ID      int
  Deleted gorm.DeletedAt
  Name    string
}

// user's ID is `111`
db.Delete(&user)
// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE id = 111;

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

// Soft deleted records will be ignored when querying
db.Where("age = 20").Find(&user)
// SELECT * FROM users WHERE age = 20 AND deleted_at IS NULL;

查找被软删除的记录:

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

GORM事务

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

Gorm提供了Transaction方法用于自动提交事务,避免用户漏写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 提交事务
  return nil
})

GORM Hook

GORM提供了CURD的Hook能力。

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

如果任何Hook返回错误,GORM将停止后续的操作并回滚事务。

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 性能提高

db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
  SkipDefaultTransaction: true,//关闭默认事务
  PrepareStmt: true //缓存预编译语句
})

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

使用PrepareStmt缓存预编译语句可以提高后续调用的速度。