Go gorm

2024-07-29 20:35:32 浏览数 (1)

Go gorm

这篇文章主要先简单总结一下gorm的crud,

什么是orm

在学习gorm之前,先了解一下什么是orm

在后端开发上,通常都要与资料库做操作(新增、修改、删除、查找),后端会撰写 SQL 语句,并且透过一些工具或套件(例如:pymysql)向 SQL 资料库来做沟通。而撰写原生 SQL 的缺点为:

  1. 不可维护性:代码难阅读且不易维护。
  2. 不可重用性:通常不容易被重用,每个 SQL 语句都需要独立编写并维护。
  3. 容易犯错:容易犯错,容易缺少引号、忘记加条件等。
  4. 容易被攻击:容易遭到 SQL Injection 攻击。
  5. 资料库迁移问题:针对 MySQL 开发的 SQL 语句就没办法直接应用到 Oracle 上的资料库。

为了解决上述问题,ORM 是再往上进行一层封装,而无需去编写原生的 SQL 语句,取而代之的是基于物件导向的思想去编写 Class、Object、Method 等。而 ORM 会再生成 SQL 语句再往下去执行

说大白话就是用类似orm.create来替代原有相对复杂的sql语句去对数据库进行操作。

ORM 优缺点

优点

提高开发效率

缺点

牺牲性能

牺牲灵活性

安装

接下来回到这篇文章的主体gorm上,这里先进行安装

代码语言:javascript复制
 go get -u gorm.io/gorm
 go get -u gorm.io/driver/sqlite

演示

代码语言:javascript复制
 package main
 ​
 import (
   "gorm.io/gorm"
   "gorm.io/driver/sqlite"
 )
 ​
 type Product struct {
   gorm.Model
   Code  string
   Price uint
 }
 ​
 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)
 }

模型声明

user模型的示例

代码语言:javascript复制
 type User struct {
   ID           uint           // Standard field for the primary key
   Name         string         // 一个常规字符串字段
   Email        *string        // 一个指向字符串的指针, allowing for null values
   Age          uint8          // 一个未签名的8位整数
   Birthday     *time.Time     // A pointer to time.Time, can be null
   MemberNumber sql.NullString // Uses sql.NullString to handle nullable strings
   ActivatedAt  sql.NullTime   // Uses sql.NullTime for nullable time fields
   CreatedAt    time.Time      // 创建时间(由GORM自动管理)
   UpdatedAt    time.Time      // 最后一次更新时间(由GORM自动管理)
 }

约定

主键:GORM 使用一个名为ID 的字段作为每个模型的默认主键。

表名:默认情况下,GORM 将结构体名称转换为 snake_case 并为表名加上复数形式。 例如,一个 User 结构体在数据库中的表名变为 users

列名:GORM 自动将结构体字段名称转换为 snake_case 作为数据库中的列名。

时间戳字段:GORM使用字段 CreatedAtUpdatedAt 来自动跟踪记录的创建和更新时间。

连接数据库

目前GORM 官方支持的数据库类型有:MySQL, PostgreSQL, SQLite, SQL Server 和 TiDB,基本把常用数据库都涵盖了。

mysql

代码语言:javascript复制
 import (
   "gorm.io/driver/mysql"
   "gorm.io/gorm"
 )
 ​
 func main() {
   // 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情
   dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
   db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
 }

PostgreSQL

代码语言:javascript复制
 import (
   "gorm.io/driver/postgres"
   "gorm.io/gorm"
 )
 ​
 dsn := "host=localhost user=gorm password=gorm dbname=gorm port=9920 sslmode=disable TimeZone=Asia/Shanghai"
 db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})

SQLite

代码语言:javascript复制
 import (
   "gorm.io/driver/sqlite" // Sqlite driver based on CGO
   // "github.com/glebarez/sqlite" // Pure go SQLite driver, checkout https://github.com/glebarez/sqlite for details
   "gorm.io/gorm"
 )
 ​
 // github.com/mattn/go-sqlite3
 db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{})

SQL Server

代码语言:javascript复制
 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{})

TiDB

TiDB 兼容 MySQL 协议。 因此你可以按照 MySQL一节来创建与 TiDB 的连接。

代码语言:javascript复制
 import (
   "fmt"
   "gorm.io/driver/mysql"
   "gorm.io/gorm"
 )
 ​
 type Product struct {
   ID    uint `gorm:"primaryKey;default:auto_random()"`
   Code  string
   Price uint
 }
 ​
 func main() {
   db, err := gorm.Open(mysql.Open("root:@tcp(127.0.0.1:4000)/test"), &gorm.Config{})
   if err != nil {
     panic("failed to connect database")
   }
 ​
   db.AutoMigrate(&Product{})
 ​
   insertProduct := &Product{Code: "D42", Price: 100}
 ​
   db.Create(insertProduct)
   fmt.Printf("insert ID: %d, Code: %s, Price: %dn",
     insertProduct.ID, insertProduct.Code, insertProduct.Price)
 ​
   readProduct := &Product{}
   db.First(&readProduct, "code = ?", "D42") // find product with code D42
 ​
   fmt.Printf("read ID: %d, Code: %s, Price: %dn",
     readProduct.ID, readProduct.Code, readProduct.Price)
 }

创建

创建记录

代码语言:javascript复制
user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}

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

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

创建多项纪录

代码语言:javascript复制
users := []*User{
    {Name: "Jinzhu", Age: 18, Birthday: time.Now()},
    {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

你无法向 ‘create’ 传递结构体,所以你应该传入数据的指针.

用指定的字段创建记录

创建记录并为指定字段赋值

代码语言:javascript复制
db.Select("Name", "Age", "CreatedAt").Create(&user)
// INSERT INTO `users` (`name`,`age`,`created_at`) VALUES ("jinzhu", 18

创建记录并忽略传递给 ‘Omit’ 的字段值

代码语言:javascript复制
db.Omit("Name", "Age", "CreatedAt").Create(&user)
// INSERT INTO `users` (`birthday`,`updated_at`) VALUES ("2020-01-01 

批量插入

要高效地插入大量记录,可以将切片传递给Create方法。 GORM 将生成一条 SQL 来插入所有数据,以返回所有主键值,并触发 Hook 方法。 当这些记录可以被分割成多个批次时,GORM会开启一个事务</0>来处理它们。

代码语言:javascript复制
var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}
db.Create(&users)

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

查询

查询单个对象

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

代码语言:javascript复制
// 获取第一条记录(主键升序)
db.First(&user)
// SELECT * FROM users ORDER BY id LIMIT 1;

// 获取一条记录,没有指定排序字段
db.Take(&user)
// SELECT * FROM users LIMIT 1;

// 获取最后一条记录(主键降序)
db.Last(&user)
// SELECT * FROM users ORDER BY id DESC LIMIT 1;

result := db.First(&user)
result.RowsAffected // 返回找到的记录数
result.Error        // returns error or nil

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

如果你想避免ErrRecordNotFound错误,你可以使用Find,比如db.Limit(1).Find(&user)Find方法可以接受struct和slice的数据。

对单个对象使用Find而不带limit,db.Find(&user)将会查询整个表并且只返回第一个对象,只是性能不高并且不确定的。

First and Last 方法会按主键排序找到第一条记录和最后一条记录 (分别)。 只有在目标 struct 是指针或者通过 db.Model() 指定 model 时,该方法才有效。 此外,如果相关 model 没有定义主键,那么将按 model 的第一个字段进行排序。 例如:

代码语言:javascript复制
var user User
var users []User

// works because destination struct is passed in
db.First(&user)
// SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1

// works because model is specified using `db.Model()`
result := map[string]interface{}{}
db.Model(&User{}).First(&result)
// SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1

// doesn't work
result := map[string]interface{}{}
db.Table("users").First(&result)

// works with Take
result := map[string]interface{}{}
db.Table("users").Take(&result)

// no primary key defined, results will be ordered by first field (i.e., `Code`)
type Language struct {
  Code string
  Name string
}
db.First(&Language{})
// SELECT * FROM `languages` ORDER BY `languages`.`code` LIMIT 1

根据主键检索

如果主键是数字类型,您可以使用 内联条件 来检索对象。 当使用字符串时,需要额外的注意来避免SQL注入;查看 Security 部分来了解详情。

代码语言:javascript复制
db.First(&user, 10)
// SELECT * FROM users WHERE id = 10;

db.First(&user, "10")
// SELECT * FROM users WHERE id = 10;

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

如果主键是字符串(例如像uuid),查询将被写成如下:

代码语言:javascript复制
db.First(&user, "id = ?", "1b74413f-f3b8-409f-ac47-e8c062e3472a")
// SELECT * FROM users WHERE id = "1b74413f-f3b8-409f-ac47-e8c062e3472a";

当目标对象有一个主键值时,将使用主键构建查询条件,例如:

代码语言:javascript复制
var user = User{ID: 10}
db.First(&user)
// SELECT * FROM users WHERE id = 10;

var result User
db.Model(User{ID: 10}).First(&result)
// SELECT * FROM users WHERE id = 10;

NOTE: 如果您使用 gorm 的特定字段类型(例如 gorm.DeletedAt),它将运行不同的查询来检索对象。

代码语言:javascript复制
type User struct {
  ID           string `gorm:"primarykey;size:16"`
  Name         string `gorm:"size:24"`
  DeletedAt    gorm.DeletedAt `gorm:"index"`
}

var user = User{ID: 15}
db.First(&user)
//  SELECT * FROM `users` WHERE `users`.`id` = '15' AND `users`.`deleted_at` IS NULL ORDER BY `users`.`id` LIMIT 1

检索全部对象

代码语言:javascript复制
// Get all records
result := db.Find(&users)
// SELECT * FROM users;

result.RowsAffected // returns found records count, equals `len(users)`
result.Error        // returns error

String 条件

代码语言:javascript复制
// Get first matched record
db.Where("name = ?", "jinzhu").First(&user)
// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;

// Get all matched records
db.Where("name <> ?", "jinzhu").Find(&users)
// SELECT * FROM users WHERE name <> 'jinzhu';

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

// Time
db.Where("updated_at > ?", lastWeek).Find(&users)
// SELECT * FROM users WHERE updated_at > '2000-01-01 00:00:00';

// BETWEEN
db.Where("created_at BETWEEN ? AND ?", lastWeek, today).Find(&users)
// SELECT * FROM users WHERE created_at BETWEEN '2000-01-01 00:00:00' AND '2000-01-08 00:00:00';

如果对象设置了主键,条件查询将不会覆盖主键的值,而是用 And 连接条件。 例如: var user = User{ID: 10} db.Where("id = ?", 20).First(&user) // SELECT * FROM users WHERE id = 10 and id = 20 ORDER BY id ASC LIMIT 1 这个查询将会给出record not found错误 所以,在你想要使用例如 user 这样的变量从数据库中获取新值前,需要将例如 id 这样的主键设置为nil。

更新

保存所有字段

Save 会保存所有的字段,即使字段是零值

代码语言:javascript复制
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;

保存 是一个组合函数。 如果保存值不包含主键,它将执行 Create,否则它将执行 Update (包含所有字段)。

代码语言:javascript复制
db.Save(&User{Name: "jinzhu", Age: 100})
// INSERT INTO `users` (`name`,`age`,`birthday`,`update_at`) VALUES ("jinzhu",100,"0000-00-00 00:00:00","0000-00-00 00:00:00")

db.Save(&User{ID: 1, Name: "jinzhu", Age: 100})
// UPDATE `users` SET `name`="jinzhu",`age`=100,`birthday`="0000-00-00 00:00:00",`update_at`="0000-00-00 00:00:00" WHERE `id` = 1

NOTE不要将 SaveModel一同使用, 这是 未定义的行为

更新单个列

当使用 Update 更新单列时,需要有一些条件,否则将会引起ErrMissingWhereClause 错误,查看 阻止全局更新 了解详情。 当使用 Model 方法,并且它有主键值时,主键将会被用于构建条件,例如:

代码语言:javascript复制
// 根据条件更新
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;

// User 的 ID 是 `111`
db.Model(&user).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;

// 根据条件和 model 的值进行更新
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;

更新多列

Updates 方法支持 structmap[string]interface{} 参数。当使用 struct 更新时,默认情况下GORM 只会更新非零值的字段

代码语言:javascript复制
// 根据 `struct` 更新属性,只会更新非零值的字段
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;

// 根据 `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;

注意 使用 struct 更新时, GORM 将只更新非零值字段。 你可能想用 map 来更新属性,或者使用 Select 声明字段来更新

更新选定字段

如果您想要在更新时选择、忽略某些字段,您可以使用 SelectOmit

代码语言:javascript复制
// 选择 Map 的字段
// User 的 ID 是 `111`:
db.Model(&user).Select("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET name='hello' WHERE id=111;

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

// 选择 Struct 的字段(会选中零值的字段)
db.Model(&user).Select("Name", "Age").Updates(User{Name: "new_name", Age: 0})
// UPDATE users SET name='new_name', age=0 WHERE id=111;

// 选择所有字段(选择包括零值字段的所有字段)
db.Model(&user).Select("*").Updates(User{Name: "jinzhu", Role: "admin", Age: 0})

// 选择除 Role 外的所有字段(包括零值字段的所有字段)
db.Model(&user).Select("*").Omit("Role").Updates(User{Name: "jinzhu", Role: "admin", Age: 0})

删除

删除一条记录

删除一条记录时,删除对象需要指定主键,否则会触发 批量删除,例如:

代码语言:javascript复制
// 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";

根据主键删除

GORM 允许通过主键(可以是复合主键)和内联条件来删除对象,它可以使用数字(如以下例子。也可以使用字符串——译者注)。查看 查询-内联条件(Query Inline Conditions) 了解详情。

代码语言:javascript复制
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);

钩子函数

对于删除操作,GORM 支持 BeforeDeleteAfterDelete Hook,在删除记录时会调用这些方法,查看 Hook 获取详情

代码语言:javascript复制
func (u *User) BeforeDelete(tx *gorm.DB) (err error) {
    if u.Role == "admin" {
        return errors.New("admin user not allowed to delete")
    }
    return
}

批量删除

如果指定的值不包括主属性,那么 GORM 会执行批量删除,它将删除所有匹配的记录

代码语言:javascript复制
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%";

可以将一个主键切片传递给Delete 方法,以便更高效的删除数据量大的记录

代码语言:javascript复制
govar users = []User{{ID: 1}, {ID: 2}, {ID: 3}}
db.Delete(&users)
// DELETE FROM users WHERE id IN (1,2,3);

db.Delete(&users, "name LIKE ?", "%jinzhu%")
// DELETE FROM users WHERE name LIKE "%jinzhu%" AND id IN (1,2,3); 

到这里就总结的差不多了,这是国人做的项目,所以中文文档很齐全,非常非常推荐去看看官方文档。

0 人点赞