【Golang】使用 gorm(ORM 框架)

Posted by 西维蜀黍 on 2020-01-12, Last Modified on 2021-09-21

Install

$ go get -u github.com/jinzhu/gorm

Demo

//数据库配置
const (
	userName = "root"
	password = "123456"
	ip       = "127.0.0.1"
	port     = "3306"
	dbName   = "test"
)

func getDBInstance() (*gorm.DB, error) {
	path := strings.Join([]string{userName, ":", password, "@tcp(", ip, ":", port, ")/", dbName, "?charset=utf8&parseTime=True&loc=Local"}, "")
	return gorm.Open("mysql", path)
}

func demo(){
	//// 自动迁移模式
	//db.AutoMigrate(&Product{})
	//
	//// 创建
	//db.Create(&Product{Code: "L1212", Price: 1000})
	//
	//// 读取
	//var product Product
	//db.First(&product, 1)                   // 查询id为1的product
	//db.First(&product, "code = ?", "L1212") // 查询code为l1212的product
	//
	//// 更新 - 更新product的price为2000
	//db.Model(&product).Update("Price", 2000)
	//
	//// 删除 - 删除product
	//db.Delete(&product)
}

Schema-related

检查表是否存在

// 检查模型`User`表是否存在
db.HasTable(&User{})

// 检查表`users`是否存在
db.HasTable("users")

创建表

// 为模型`User`创建表
db.CreateTable(&User{})

// 创建表`users'时将“ENGINE = InnoDB”附加到SQL语句
db.Set("gorm:table_options", "ENGINE=InnoDB").CreateTable(&User{})

删除表

// 删除模型`User`的表
db.DropTable(&User{})

// 删除表`users`
db.DropTable("users")

// 删除模型`User`的表和表`products`
db.DropTableIfExists(&User{}, "products")

修改列

修改列的类型为给定值

// 修改模型`User`的description列的数据类型为`text`
db.Model(&User{}).ModifyColumn("description", "text")

删除列

// 删除模型`User`的description列
db.Model(&User{}).DropColumn("description")

添加外键

// 添加主键
// 1st param : 外键字段
// 2nd param : 外键表(字段)
// 3rd param : ONDELETE
// 4th param : ONUPDATE
db.Model(&User{}).AddForeignKey("city_id", "cities(id)", "RESTRICT", "RESTRICT")

索引

// 为`name`列添加索引`idx_user_name`
db.Model(&User{}).AddIndex("idx_user_name", "name")

// 为`name`, `age`列添加索引`idx_user_name_age`
db.Model(&User{}).AddIndex("idx_user_name_age", "name", "age")

// 添加唯一索引
db.Model(&User{}).AddUniqueIndex("idx_user_name", "name")

// 为多列添加唯一索引
db.Model(&User{}).AddUniqueIndex("idx_user_name_age", "name", "age")

// 删除索引
db.Model(&User{}).RemoveIndex("idx_user_name")

CRUD

Retrieve

First

查询一条记录,根据主键ID排序(正序),返回第一条记录

//等价于:SELECT * FROM `foods`   ORDER BY `foods`.`id` ASC LIMIT 1    
db.First(&food)

Last

查询一条记录, 根据主键ID排序(倒序),返回第一条记录

//等价于:SELECT * FROM `foods`   ORDER BY `foods`.`id` DESC LIMIT 1   
//语义上相当于返回最后一条记录
db.Last(&food)

Find

查询多条记录,Find函数返回的是一个数组

//因为Find返回的是数组,所以定义一个商品数组用来接收结果
var foods []Food

//等价于:SELECT * FROM `foods`
db.Find(&foods)

// 使用主键获取记录
db.First(&user, 10)
//// SELECT * FROM users WHERE id = 10;

Where查询条件 (简单SQL)

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

// 获取所有匹配记录
db.Where("name = ?", "jinzhu").Find(&users)
//// SELECT * FROM users WHERE name = 'jinzhu';

db.Where("name <> ?", "jinzhu").Find(&users)

// IN
db.Where("name in (?)", []string{"jinzhu", "jinzhu 2"}).Find(&users)

// LIKE
db.Where("name LIKE ?", "%jin%").Find(&users)

// AND
db.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users)

// Time
db.Where("updated_at > ?", lastWeek).Find(&users)

db.Where("created_at BETWEEN ? AND ?", lastWeek, today).Find(&users)

Where查询条件 (Struct & Map)

注意:当使用struct查询时,GORM将只查询那些具有值的字段

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

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

// 主键的Slice
db.Where([]int64{20, 21, 22}).Find(&users)
//// SELECT * FROM users WHERE id IN (20, 21, 22);

Create

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

db.NewRecord(user) // => 主键为空返回`true`

db.Create(&user)

db.NewRecord(user) // => 创建`user`后返回`false`

默认值

您可以在gorm tag中定义默认值,然后插入SQL将忽略具有默认值的这些字段,并且其值为空,并且在将记录插入数据库后,gorm将从数据库加载这些字段的值。

type Animal struct {
    ID   int64
    Name string `gorm:"default:'galeone'"`
    Age  int64
}

var animal = Animal{Age: 99, Name: ""}
db.Create(&animal)
// INSERT INTO animals("age") values('99');
// SELECT name from animals WHERE ID=111; // 返回主键为 111
// animal.Name => 'galeone'

在Callbacks中设置主键

如果要在BeforeCreate回调中设置主字段的值,可以使用scope.SetColumn,例如:

func (user *User) BeforeCreate(scope *gorm.Scope) error {
  scope.SetColumn("ID", uuid.New())
  return nil
}

扩展创建选项

// 为Instert语句添加扩展SQL选项
db.Set("gorm:insert_option", "ON CONFLICT").Create(&product)
// INSERT INTO products (name, code) VALUES ("name", "code") ON CONFLICT;

Update

更新所有字段

Save会更新所有字段,即使你没有赋值

db.First(&user)

user.Name = "jinzhu 2"
user.Age = 100
db.Save(&user)

//// UPDATE users SET name='jinzhu 2', age=100, birthday='2016-01-01', updated_at = '2013-11-17 21:34:10' WHERE id=111;

更新修改字段

如果你只希望更新指定字段,可以使用Update或者Updates

// 更新单个属性,如果它有变化
db.Model(&user).Update("name", "hello")
//// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;

// 根据给定的条件更新单个属性
db.Model(&user).Where("active = ?", true).Update("name", "hello")
//// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111 AND active=true;

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

// 使用 struct 更新多个属性,只会更新其中有变化且为非零值的字段
db.Model(&user).Updates(User{Name: "hello", Age: 18})
//// UPDATE users SET name='hello', age=18, updated_at = '2013-11-17 21:34:10' WHERE id = 111;

// 警告:当使用 struct 更新时,GORM只会更新那些非零值的字段
// 对于下面的操作,不会发生任何更新,"", 0, false 都是其类型的零值
db.Model(&user).Updates(User{Name: "", Age: 0, Actived: false})

更新选定字段

如果你想更新或忽略某些字段,你可以使用 SelectOmit

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

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

无 Hooks 更新

上面的更新操作会自动运行 model 的 BeforeUpdate, AfterUpdate 方法,更新 UpdatedAt 时间戳, 在更新时保存其 Associations, 如果你不想调用这些方法,你可以使用 UpdateColumnUpdateColumns

// 更新单个属性,类似于 `Update`
db.Model(&user).UpdateColumn("name", "hello")
//// UPDATE users SET name='hello' WHERE id = 111;

// 更新多个属性,类似于 `Updates`
db.Model(&user).UpdateColumns(User{Name: "hello", Age: 18})
//// UPDATE users SET name='hello', age=18 WHERE id = 111;

批量更新

批量更新时 Hooks 不会运行

db.Table("users").Where("id IN (?)", []int{10, 11}).Updates(map[string]interface{}{"name": "hello", "age": 18})
//// UPDATE users SET name='hello', age=18 WHERE id IN (10, 11);

// 使用 struct 更新时,只会更新非零值字段,若想更新所有字段,请使用map[string]interface{}
db.Model(User{}).Updates(User{Name: "hello", Age: 18})
//// UPDATE users SET name='hello', age=18;

// 使用 `RowsAffected` 获取更新记录总数
db.Model(User{}).Updates(User{Name: "hello", Age: 18}).RowsAffected

使用 SQL 表达式更新

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" = '2';

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" = '2';

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

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

错误处理

错误处理

GORM中的错误处理与惯用的Go代码不同,因为它具有可链接的API,但仍然易于实现。

如果发生任何错误,GORM将设置* gorm.DB的错误字段,可以这样检查:

if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil {
  // error 处理...
}

或者

if result := db.Where("name = ?", "jinzhu").First(&user); result.Error != nil {
  // error 处理...
}

错误

处理数据时,通常会发生多个错误。 GORM提供了一个API来将所有错误作为切片返回:

// 如果发生了一个以上的错误, `GetErrors` 以`[]error`形式返回他们
errors := db.First(&user).Limit(10).Find(&users).GetErrors()

fmt.Println(len(errors))

for _, err := range errors {
  fmt.Println(err)
}

记录未找到错误

RecordNotFound,GORM提供了处理 RecordNotFound 错误的快捷方式。如果有多个错误,它将逐一检查这些错误是否为 RecordNotFound 错误。

// 检查是否为 RecordNotFound 错误
db.Where("name = ?", "hello world").First(&user).RecordNotFound()

if db.Model(&user).Related(&credit_card).RecordNotFound() {
  // 未找到记录
}

if err := db.Where("name = ?", "jinzhu").First(&user).Error; gorm.IsRecordNotFoundError(err) {
  // 未找到记录
}

Potential Error

错误 1 - sql: unknown driver “mysql”

sql: unknown driver "mysql" (forgotten import?)
panic: runtime error: invalid memory address or nil pointer dereference [recovered]
        panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x20 pc=0x10dd1f1]

solution

import(
database/sql
// 引入数据库驱动注册及初始化
	_ "github.com/go-sql-driver/mysql"
)

Reference