课程笔记:GORM框架 | 青训营

81 阅读7分钟

Gorm (ORM框架)

基本使用

示例代码:

package main  
  
import (  
  "gorm.io/gorm"  
  "gorm.io/driver/sqlite"  
)  
  
type Product struct {  
  gorm.Model  
  Code  string  
  Price uint  
}

// 为model定义表名
func (p Product) TableName() string{
	return "product"
}
  
func main() {  
  db, err := gorm.Open(
  mysql.Open(dsn:"user:pass@top(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"), 
  &gorm.Config{})  
  if err != nil {  
    panic("failed to connect database")  
  }  
  ... // 各种操作  
}

约定:

  1. Gorm使用名为ID的字段作为主键
  2. 使用结构体的蛇形作为列名
  3. 使用CreatedAt, UpdatedAt字段作为创建、更新时间

以上代码中包括如下步骤:

  1. 定义gorm model,对应数据库中的一张表,字段对应表中字段,如果没有实现Tablename,就会使用蛇形负数
  2. 为model定义表名,
  3. 通过Open连接数据库Mysql,同时通过gorm.Config添加一些配置
  4. 使用Creat创建数据,创建单个数据时传递对象,创建多条数据时传递切片,
  5. 查询数据时先申明一个结构体,再通过First查询单条记录并返回到这个结构体里同时传递一个查询条件,1指根据整形逐渐查找
  6. 使用Update更新单条数据,使用Updates更新多条数据,使用map时可以更新零值
  7. 通过Delete删除,支持传入一个结构体以及条件

DSN

DSN 是数据库源名称(Data Source Name)的缩写,它是一个字符串,用于唯一标识数据库并提供连接细节。

DSN 通常包含以下信息:

  • 数据库类型(MySQL,SQL Server,Oracle等)
  • 服务器名称或 IP 地址
  • 端口号
  • 数据库名称
  • 用户名和密码(可选)

例如,一个 MySQL DSN 可能如下所示:

mysql://username:password@localhost:3306/mydatabase

这个 DSN 指定了:

  • 数据库类型:mysql
  • 用户名:username
  • 密码:password
  • 服务器:localhost
  • 端口:3306
  • 数据库名称:mydatabase

创建数据

创建结构体

type Product struct {  
  ID    uint   `gorm:"primarykey"`
  Code  string `gorm:"column: code"`
  Price uint   `gorm:"column user_id"`
}

创建一条数据

p := &Product{Code: "D42"}
res := db.Creat(p)
fmt.Println(res.Error) // 获取error
fmt.Println(p.ID)      // 返回插入数据的主键

因为gorm是链式调用,会返回一个gorm对象,可以用这个来获取error和主键

没有传主键时可以设置自动主键

创建多条数据

使用list创建

products := []*Product{{Code: "D41"}, {Code: "D42"}, {Code: "D43"}}
res := db.Creat(products)
fmt.Println(res.Error) // 获取err
for _, p := range products {
	fmt.Println(p.ID)
}

Upsert中唯一索引冲突

使用clause.OnConfict处理数据冲突,以下以不处理冲突为例创建一条数据

p := &Product{Code: "D42", ID: 1}
db.Clauses(clause.OnConfict{DoNothing: true}).Creat(&p)

DoNothing: true即表示遇见冲突时不处理

主要参数包含:

  • Columns: 定义唯一索引的列,用于唯一性检查。
  • DoUpdates: 设置在冲突时要更新的字段,类似ON CONFLICT DO UPDATE。
  • DoNothing: 如果为true,在冲突时啥也不做,忽略错误。

使用默认值

通过default标签为字段定义默认值 示例:

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

注意:

  • default只在创建时插入默认值,不会在更新时自动更新。
  • 默认值不会用于读操作,只会返回实际保存的值。
  • 默认值不会用于构造器和更新All。

查询数据

GORM 提供了 FirstTakeLast 方法,以便从数据库中检索单个对象。当查询数据库时它添加了 LIMIT 1 条件,且没有找到记录时,它会返回 ErrRecordNotFound 错误

获得第一条数据

// (主键升序)  
db.First(&user)  
// SELECT * FROM users ORDER BY id LIMIT 1;  

// 检查 ErrRecordNotFound 错误  
errors.Is(result.Error, gorm.ErrRecordNotFound)

注意:First查询不到数据时会返回ErrRecordNotFound,可以代替使用Find,Find会返回空数组而不是错误

查询多条数据

users := make([]*User, 0)
result := db.Where(query:"age > 10").Find(&users) // SELECT * FROM users where age > 10;
fmt.Println(result.RowAffected)
fmt.Println(result.Error) // return error

通过where传入查询条件在调用Find查询,再通过result.RowAffected返回找到的记录数,相当于len(users)

string条件

// IN SELECT * FROM users WHERE name IN ('jinzhu','jinzhu 2');
db.Where(query:“name IN?", []string{"jinzhu", "jinzhu 2"})
// LIKE SELECT * FROM users WHERE name LIKE `%jin%`;
db.Where(query:"name = ?", args…:"%jin%").Find(&users)
// AND SELECT * FROM users WHERE name = 'jinzhu' AND age >= 22;
db.Where(query:"name = ? AND age >= ?", args…:"jinzhu", "22").Find(&users)

关于LIKE注意点:

  • LIKE默认是区分大小写的,可以使用ILIKE实现大小写不敏感的模糊查询。
  • LIKE通常需要结合占位符?使用,避免SQL注入。
  • 可以使用gorm.Expr实现OR联合的LIKE查询。
  • 上述LIKE代码中%表示被省略的内容

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

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

更新数据

条件更新单个列

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;

需要使用Model去设置表名

更新多个列

根据 struct 更新属性

db.Model(&User{ID: 111}).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;  

没有where查询条件时用主键ID的值进行更新

使用 map 更新属性

db.Model(&User{ID: 111}).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更新时,只会更新非零值,如果需要更新零值可以使用Map更新或使用Select选择字段

更新选定字段

使用Select API选定name更新

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

产出数据

物理删除(从数据库中真正删除)

直接使用Delet删除

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

传递字符串或者是整数都会将其识别成主键

通过列表

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

条件查询

db.Where("name LIKE ?", "%jinzhu%").Delete(User{}) //DELETE FROM users where name LIKE "%jinzhu%";
db.Delete(User{}, "email LIKE ?", "%jinzhu%") //DELETE FROM users where name LIKE "%jinzhu%";

软删除

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

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

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

// 删除一条
u := User{ID: 111}
db.Delete(&u)
// 批量删除
db.Where("age = ?", 20).Delete($User{})
uers := make([]*User, 0)
//查询时不会忽略被删除的记录
db.Unscoped().Where("age = 20").Find(&users)

事务

  • 开始事务

    tx := db.Begin()
    

    在食物中使用一些db操作时不能使用 tx 而是 db ,因为开始操作会固化一个链接

  • 回滚事务 tx.Rollback()

  • 提交事务 tx.Commit()

  • Transaction方法自动提交事务,避免用户漏写Commit, Rollback

    db.Transaction(func(tx *gorm.DB) error {  
    if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
    // 返回 err Rollback事务  
    return err  
    }   
    if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {  
    return err  
    }  
    // 返回 nil 提交事务  
    return nil  
    

}) ```

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 中保存、删除操作会默认运行在事务上, 因此在事务完成之前该事务中所作的更改是不可见的,如果钩子返回了任何错误,则修改将被回滚。

func (u *User) AfterCreate(tx *gorm.DB) (err error) {  
	if !u.IsValid() {  
		return errors.New("rollback invalid user")  
	}  
	return nil  
}